using System; using System.IO; using System.Collections.Generic; using System.Net.Sockets; using System.Diagnostics; namespace Apewer.Source { /// public class Redis : IDisposable { Socket socket; BufferedStream bstream; int db = 0; /// 连接指定主机。 public Redis(string host, int port = 6379, string password = null) { Host = string.IsNullOrEmpty(host) ? "localhost" : host; Port = (port < 1 || port > 65535) ? 6379 : port; Password = password; SendTimeout = -1; } /// 连接 localhost 的指定端口。 public Redis(int port, string host = "localhost", string password = null) : this(host, port, password) { } /// 连接 localhost 的 6379 端口。 public Redis() : this("localhost", 6379, null) { } /// 主机。 public string Host { get; private set; } /// 端口。 public int Port { get; private set; } /// 重试超时。 public int RetryTimeout { get; set; } /// 重试次数。 public int RetryCount { get; set; } /// Socket 发送超时。 public int SendTimeout { get; set; } /// 密码。 public string Password { get; set; } /// 设置要日志输出。 public Action Log { get; set; } /// 数据库编号,默认为 0。 public int Db { get { return db; } set { db = value; SendExpectSuccess("SELECT", db); } } /// 获取或设置文本。 public string this[string key] { get { return GetString(key); } set { Set(key, value); } } /// 设置 UTF-8 文本。返回错误信息。 public string Set(string key, string value, bool ifNotExist = false) { if (key == null) return "Key 无效。"; if (value == null) return "Value 无效"; var bytes = value.Length < 1 ? new byte[0] : GetBytes(value); return Set(key, bytes, ifNotExist); } /// 设置字节数组,Bytes 最大长度为 1073741824。返回错误信息。 public string Set(string key, byte[] bytes, bool ifNotExist = false) { if (key == null) return "Key 无效。"; if (bytes == null) return "Bytes 无效"; if (bytes.Length > 1073741824) return "Value[] 长度超出限制。"; var cmd = ifNotExist ? "SETNX" : "SET"; var success = SendDataCommand(bytes, cmd, key); if (!success) return "设置失败。"; return ExpectSuccess(); } /// 设置多个值。返回错误信息。 public string Set(IDictionary dict) { if (dict == null) return "字典无效。"; var newDict = new Dictionary(); foreach (var i in dict) newDict.Add(i.Key, GetBytes(i.Value)); return Set(newDict); // return Set(dict.ToDictionary(k => k.Key, v => GetBytes(v.Value))); } /// 设置多个值。返回错误信息。 public string Set(IDictionary dict) { if (dict == null) return "字典无效。"; var keys = new List(); var values = new List(); foreach (var i in dict) { keys.Add(i.Key); values.Add(i.Value); } return Set(keys.ToArray(), values.ToArray()); //return Set(dict.Keys.ToArray(), dict.Values.ToArray()); } /// 设置多个值。返回错误信息。 public string Set(string[] keys, byte[][] values) { if (keys == null) return "Keys 无效。"; if (values == null) return "Values 无效"; if (keys.Length != values.Length) return "Keys 和 Values 的长度不相等"; byte[] nl = GetBytes("\r\n"); var ms = new MemoryStream(); for (int i = 0; i < keys.Length; i++) { byte[] key = GetBytes(keys[i]); byte[] val = values[i]; byte[] kLength = GetBytes("$" + key.Length + "\r\n"); byte[] k = GetBytes(keys[i] + "\r\n"); byte[] vLength = GetBytes("$" + val.Length + "\r\n"); ms.Write(kLength, 0, kLength.Length); ms.Write(k, 0, k.Length); ms.Write(vLength, 0, vLength.Length); ms.Write(val, 0, val.Length); ms.Write(nl, 0, nl.Length); } SendDataRESP(ms.ToArray(), "*" + (keys.Length * 2 + 1) + "\r\n$4\r\nMSET\r\n"); return ExpectSuccess(); } /// 获取值。Key 无效时获取 NULL 值。 public byte[] Get(string key) { if (key == null) return null; return SendExpectData("GET", key); } /// 获取值。Key 无效时获取 NULL 值。 public string GetString(string key) { if (key == null) return null; var bytes = Get(key); if (bytes == null) return null; return GetString(bytes); } /// 排序。 public byte[][] Sort(string key = null, string storeInKey = null, bool descending = false, bool lexographically = false, int lowerLimit = 0, int upperLimit = 0, string by = null, string get = null ) { var args = new System.Collections.ArrayList(); if (lowerLimit != 0 || upperLimit != 0) { args.Add("LIMIT"); args.Add(lowerLimit); args.Add(upperLimit); } if (lexographically) args.Add("ALPHA"); if (!string.IsNullOrEmpty(by)) { args.Add("BY"); args.Add(by); } if (!string.IsNullOrEmpty(get)) { args.Add("GET"); args.Add(get); } var argsArray = args.ToArray(); return Sort(key, storeInKey, argsArray); } // public byte[][] Sort(RedisSortOptions options) // { // return Sort(options.Key, options.StoreInKey, options.ToArgs()); // } /// 排序。Key 无效时返回 NULL 值。 public byte[][] Sort(string key, string destination, params object[] options) { if (key == null) return null; // throw new ArgumentNullException("key"); int offset = string.IsNullOrEmpty(destination) ? 1 : 3; object[] args = new object[offset + options.Length]; args[0] = key; Array.Copy(options, 0, args, offset, options.Length); if (offset == 1) { return SendExpectDataArray("SORT", args); } else { args[1] = "STORE"; args[2] = destination; int n = SendExpectInt("SORT", args); return new byte[n][]; } } /// 获取集合。获取失败时返回 NULL 值。 public byte[] GetSet(string key, byte[] value) { if (key == null) return null; // throw new ArgumentNullException("key"); if (value == null) return null; // throw new ArgumentNullException("value"); if (value.Length > 1073741824) return null; // throw new ArgumentException("value exceeds 1G", "value"); if (!SendDataCommand(value, "GETSET", key)) return null; // throw new Exception("Unable to connect"); return ReadData(); } /// 获取集合。获取失败时返回 NULL 值。 public string GetSet(string key, string value) { if (key == null) return null; // throw new ArgumentNullException("key"); if (value == null) return null; // throw new ArgumentNullException("value"); return GetString(GetSet(key, GetBytes(value))); } string ReadLine() { var sb = new System.Text.StringBuilder(); int c; while ((c = bstream.ReadByte()) != -1) { if (c == '\r') continue; if (c == '\n') break; sb.Append((char)c); } return sb.ToString(); } string Connect() { try { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.NoDelay = true; socket.SendTimeout = SendTimeout; socket.Connect(Host, Port); if (!socket.Connected) { socket.Close(); socket = null; return "连接失败。"; } bstream = new BufferedStream(new NetworkStream(socket), 16 * 1024); if (Password != null) return SendExpectSuccess("AUTH", Password); return null; } catch (Exception ex) { var error = ex.GetType().Name + ": " + ex.Message; return error; } } byte[] end_data = new byte[] { (byte)'\r', (byte)'\n' }; bool SendDataCommand(byte[] data, string cmd, params object[] args) { string resp = "*" + (1 + args.Length + 1).ToString() + "\r\n"; resp += "$" + cmd.Length + "\r\n" + cmd + "\r\n"; foreach (object arg in args) { string argStr = arg.ToString(); int argStrLength = GetByteCount(argStr); resp += "$" + argStrLength + "\r\n" + argStr + "\r\n"; } resp += "$" + data.Length + "\r\n"; return SendDataRESP(data, resp); } bool SendDataRESP(byte[] data, string resp) { if (socket == null) Connect(); if (socket == null) return false; byte[] r = GetBytes(resp); try { ShowLog("C", resp); socket.Send(r); if (data != null) { socket.Send(data); socket.Send(end_data); } } catch (SocketException) { // timeout; socket.Close(); socket = null; return false; } return true; } bool SendCommand(string cmd, params object[] args) { if (socket == null) Connect(); if (socket == null) return false; string resp = "*" + (1 + args.Length).ToString() + "\r\n"; resp += "$" + cmd.Length + "\r\n" + cmd + "\r\n"; foreach (object arg in args) { string argStr = arg.ToString(); int argStrLength = GetByteCount(argStr); resp += "$" + argStrLength + "\r\n" + argStr + "\r\n"; } byte[] r = GetBytes(resp); try { ShowLog("C", resp); socket.Send(r); } catch (SocketException) { // timeout; socket.Close(); socket = null; return false; } return true; } [Conditional("DEBUG")] void ShowLog(string id, string message) { Log?.Invoke(id + ": " + message.Trim().Replace("\r\n", " ")); } string ExpectSuccess() { int c = bstream.ReadByte(); if (c == -1) return "没有更多字节。"; string s = ReadLine(); ShowLog("S", (char)c + s); if (c == '-') return s.StartsWith("ERR ") ? s.Substring(4) : s; return null; } string SendExpectSuccess(string cmd, params object[] args) { if (!SendCommand(cmd, args)) return "无法连接。"; // throw new Exception("Unable to connect"); return ExpectSuccess(); } int SendDataExpectInt(byte[] data, string cmd, params object[] args) { if (!SendDataCommand(data, cmd, args)) return 0; // throw new Exception("Unable to connect"); int c = bstream.ReadByte(); if (c == -1) return 0; // throw new ResponseException("No more data"); string s = ReadLine(); ShowLog("S", (char)c + s); if (c == '-') return 0; // throw new ResponseException(s.StartsWith("ERR ") ? s.Substring(4) : s); if (c == ':') { int i; if (int.TryParse(s, out i)) return i; } return 0; // throw new ResponseException("Unknown reply on integer request: " + c + s); } int SendExpectInt(string cmd, params object[] args) { if (!SendCommand(cmd, args)) return 0; // throw new Exception("Unable to connect"); int c = bstream.ReadByte(); if (c == -1) return 0; // throw new ResponseException("No more data"); string s = ReadLine(); ShowLog("S", (char)c + s); if (c == '-') return 0; // throw new ResponseException(s.StartsWith("ERR ") ? s.Substring(4) : s); if (c == ':') { int i; if (int.TryParse(s, out i)) return i; } return 0; // throw new ResponseException("Unknown reply on integer request: " + c + s); } string SendExpectString(string cmd, params object[] args) { if (!SendCommand(cmd, args)) return null; // throw new Exception("Unable to connect"); int c = bstream.ReadByte(); if (c == -1) return null; // throw new ResponseException("No more data"); string s = ReadLine(); ShowLog("S", (char)c + s); if (c == '-') return null; // throw new ResponseException(s.StartsWith("ERR ") ? s.Substring(4) : s); if (c == '+') return s; return null; // throw new ResponseException("Unknown reply on integer request: " + c + s); } string SendGetString(string cmd, params object[] args) { if (!SendCommand(cmd, args)) return null; // 连接失败时返回 NULL 值。 return ReadLine(); } byte[] SendExpectData(string cmd, params object[] args) { if (!SendCommand(cmd, args)) return null; // 连接失败时返回 NULL 值。 return ReadData(); } byte[] ReadData() { string s = ReadLine(); ShowLog("S", s); if (s.Length == 0) return null; // 失败时返回 NULL 值。不 Throw 异常。 char c = s[0]; if (c == '-') return null; // throw new ResponseException(s.StartsWith("-ERR ") ? s.Substring(5) : s.Substring(1)); if (c == '$') { if (s == "$-1") return null; int n; if (Int32.TryParse(s.Substring(1), out n)) { byte[] retbuf = new byte[n]; int bytesRead = 0; do { int read = bstream.Read(retbuf, bytesRead, n - bytesRead); if (read < 1) return null; // throw new ResponseException("Invalid termination mid stream"); bytesRead += read; } while (bytesRead < n); if (bstream.ReadByte() != '\r' || bstream.ReadByte() != '\n') return null; // throw new ResponseException("Invalid termination"); return retbuf; } return null; // throw new ResponseException("Invalid length"); } /* don't treat arrays here because only one element works -- use DataArray! //returns the number of matches if (c == '*') { int n; if (Int32.TryParse(s.Substring(1), out n)) return n <= 0 ? new byte [0] : ReadData(); throw new ResponseException ("Unexpected length parameter" + r); } */ return null; // throw new ResponseException("Unexpected reply: " + s); } /// public bool ContainsKey(string key) { if (key == null) return false; // throw new ArgumentNullException("key"); return SendExpectInt("EXISTS", key) == 1; } /// public bool Remove(string key) { if (key == null) return false; // throw new ArgumentNullException("key"); return SendExpectInt("DEL", key) == 1; } /// public int Remove(params string[] args) { if (args == null) return 0; // throw new ArgumentNullException("args"); return SendExpectInt("DEL", args); } /// public int Increment(string key) { if (key == null) return 0; // throw new ArgumentNullException("key"); return SendExpectInt("INCR", key); } /// public int Increment(string key, int count) { if (key == null) return 0; // throw new ArgumentNullException("key"); return SendExpectInt("INCRBY", key, count); } /// public int Decrement(string key) { if (key == null) return 0; // throw new ArgumentNullException("key"); return SendExpectInt("DECR", key); } /// public int Decrement(string key, int count) { if (key == null) return 0; // throw new ArgumentNullException("key"); return SendExpectInt("DECRBY", key, count); } /// 获取 Key 的值类型,正确类型为 none | string | set | list。 public string TypeOf(string key) { if (key == null) return ""; return SendExpectString("TYPE", key); } /// public string RandomKey() { return SendExpectString("RANDOMKEY"); } /// public bool Rename(string oldKeyname, string newKeyname) { if (oldKeyname == null) return false; // throw new ArgumentNullException("oldKeyname"); if (newKeyname == null) return false; // throw new ArgumentNullException("newKeyname"); return SendGetString("RENAME", oldKeyname, newKeyname)[0] == '+'; } /// public bool Expire(string key, int seconds) { if (key == null) return false; // throw new ArgumentNullException("key"); return SendExpectInt("EXPIRE", key, seconds) == 1; } /// public bool ExpireAt(string key, int time) { if (key == null) return false;// throw new ArgumentNullException("key"); return SendExpectInt("EXPIREAT", key, time) == 1; } /// public int TimeToLive(string key) { if (key == null) return 0; // throw new ArgumentNullException("key"); return SendExpectInt("TTL", key); } /// public int DbSize { get { return SendExpectInt("DBSIZE"); } } /// public void Save() { SendExpectSuccess("SAVE"); } /// public void BackgroundSave() { SendExpectSuccess("BGSAVE"); } /// public void Shutdown() { SendCommand("SHUTDOWN"); try { // the server may return an error string s = ReadLine(); ShowLog("S", s); if (s.Length == 0) return; // throw new ResponseException("Zero length respose"); // throw new ResponseException(s.StartsWith("-ERR ") ? s.Substring(5) : s.Substring(1)); } catch (IOException) { // this is the expected good result socket.Close(); socket = null; } } /// public void FlushAll() { SendExpectSuccess("FLUSHALL"); } /// public void FlushDb() { SendExpectSuccess("FLUSHDB"); } const long UnixEpoch = 621355968000000000L; /// public DateTime LastSave { get { int t = SendExpectInt("LASTSAVE"); return new DateTime(UnixEpoch) + TimeSpan.FromSeconds(t); } } /// public Dictionary GetInfo() { byte[] r = SendExpectData("INFO"); var dict = new Dictionary(); foreach (var line in GetString(r).Split('\n')) { int p = line.IndexOf(':'); if (p == -1) continue; dict.Add(line.Substring(0, p), line.Substring(p + 1)); } return dict; } /// public string[] Keys { get { return GetKeys("*"); } } /// public string[] GetKeys(string pattern) { if (pattern == null) return null; // throw new ArgumentNullException("pattern"); return SendExpectStringArray("KEYS", pattern); } /// public byte[][] MGet(params string[] keys) { if (keys == null) return null; // throw new ArgumentNullException("keys"); if (keys.Length == 0) return null; // throw new ArgumentException("keys"); return SendExpectDataArray("MGET", keys); } /// public string[] SendExpectStringArray(string cmd, params object[] args) { byte[][] reply = SendExpectDataArray(cmd, args); string[] keys = new string[reply.Length]; for (int i = 0; i < reply.Length; i++) keys[i] = GetString(reply[i]); return keys; } /// public byte[][] SendExpectDataArray(string cmd, params object[] args) { if (!SendCommand(cmd, args)) return null; // throw new Exception("Unable to connect"); int c = bstream.ReadByte(); if (c == -1) return null; // throw new ResponseException("No more data"); string s = ReadLine(); ShowLog("S", (char)c + s); if (c == '-') return null; // throw new ResponseException(s.StartsWith("ERR ") ? s.Substring(4) : s); if (c == '*') { int count; if (int.TryParse(s, out count)) { byte[][] result = new byte[count][]; for (int i = 0; i < count; i++) result[i] = ReadData(); return result; } } return null; // throw new ResponseException("Unknown reply on multi-request: " + c + s); } #region List commands /// public byte[][] ListRange(string key, int start, int end) { return SendExpectDataArray("LRANGE", key, start, end); } /// public void LeftPush(string key, string value) { LeftPush(key, GetBytes(value)); } /// public void LeftPush(string key, byte[] value) { SendDataCommand(value, "LPUSH", key); ExpectSuccess(); } /// public void RightPush(string key, string value) { RightPush(key, GetBytes(value)); } /// public void RightPush(string key, byte[] value) { SendDataCommand(value, "RPUSH", key); ExpectSuccess(); } /// public int ListLength(string key) { return SendExpectInt("LLEN", key); } /// public byte[] ListIndex(string key, int index) { SendCommand("LINDEX", key, index); return ReadData(); } /// public byte[] LeftPop(string key) { SendCommand("LPOP", key); return ReadData(); } /// public byte[] RightPop(string key) { SendCommand("RPOP", key); return ReadData(); } #endregion #region Set commands /// public bool AddToSet(string key, byte[] member) { return SendDataExpectInt(member, "SADD", key) > 0; } /// public bool AddToSet(string key, string member) { return AddToSet(key, GetBytes(member)); } /// public int CardinalityOfSet(string key) { return SendExpectInt("SCARD", key); } /// public bool IsMemberOfSet(string key, byte[] member) { return SendDataExpectInt(member, "SISMEMBER", key) > 0; } /// public bool IsMemberOfSet(string key, string member) { return IsMemberOfSet(key, GetBytes(member)); } /// public byte[][] GetMembersOfSet(string key) { return SendExpectDataArray("SMEMBERS", key); } /// public byte[] GetRandomMemberOfSet(string key) { return SendExpectData("SRANDMEMBER", key); } /// public byte[] PopRandomMemberOfSet(string key) { return SendExpectData("SPOP", key); } /// public bool RemoveFromSet(string key, byte[] member) { return SendDataExpectInt(member, "SREM", key) > 0; } /// public bool RemoveFromSet(string key, string member) { return RemoveFromSet(key, GetBytes(member)); } /// public byte[][] GetUnionOfSets(params string[] keys) { if (keys == null) return null; // throw new ArgumentNullException(); return SendExpectDataArray("SUNION", keys); } void StoreSetCommands(string cmd, params string[] keys) { if (String.IsNullOrEmpty(cmd)) return; // throw new ArgumentNullException("cmd"); if (keys == null) return; // throw new ArgumentNullException("keys"); SendExpectSuccess(cmd, keys); } /// public void StoreUnionOfSets(params string[] keys) { StoreSetCommands("SUNIONSTORE", keys); } /// public byte[][] GetIntersectionOfSets(params string[] keys) { if (keys == null) return null; // throw new ArgumentNullException(); return SendExpectDataArray("SINTER", keys); } /// public void StoreIntersectionOfSets(params string[] keys) { StoreSetCommands("SINTERSTORE", keys); } /// public byte[][] GetDifferenceOfSets(params string[] keys) { if (keys == null) return null; // throw new ArgumentNullException(); return SendExpectDataArray("SDIFF", keys); } /// public void StoreDifferenceOfSets(params string[] keys) { StoreSetCommands("SDIFFSTORE", keys); } /// public bool MoveMemberToSet(string srcKey, string destKey, byte[] member) { return SendDataExpectInt(member, "SMOVE", srcKey, destKey) > 0; } #endregion /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// ~Redis() { Dispose(false); } /// protected virtual void Dispose(bool disposing) { if (disposing) { SendCommand("QUIT"); ExpectSuccess(); socket.Close(); socket = null; } } private static byte[] GetBytes(string text) { if (string.IsNullOrEmpty(text)) return new byte[0]; return System.Text.Encoding.UTF8.GetBytes(text); } private static string GetString(byte[] bytes) { if (bytes != null && bytes.Length > 0) { try { return System.Text.Encoding.UTF8.GetString(bytes); } catch { } } return ""; } private static int GetByteCount(string text) { if (string.IsNullOrEmpty(text)) return 0; return System.Text.Encoding.UTF8.GetByteCount(text); } } }