You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

747 lines
24 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
namespace Apewer.Source
{
/// <summary></summary>
public sealed class Redis : IDisposable
{
/// <summary>最大长度。</summary>
public const int MaxLength = 1073741824;
private const int BufferSize = 1048576;
#region Instance
string _host = null;
string _pass = null;
int _port = 6379;
int _db = 0;
int _timeout = 1000; // 1000 毫秒。
Socket socket;
BufferedStream buffered;
/// <summary>连接指定主机。</summary>
public Redis(string host, int port = 6379, string password = null)
{
_host = string.IsNullOrEmpty(host) ? "localhost" : host;
_port = (port < 1 || port > 65535) ? 6379 : port;
_pass = password;
_timeout = -1;
}
/// <summary>连接 127.0.0.1 的指定端口。</summary>
public Redis(int port, string host = "127.0.0.1", string password = null) : this(host, port, password) { }
/// <summary>连接 127.0.0.1 的 6379 端口。</summary>
public Redis() : this("127.0.0.1", 6379, null) { }
/// <summary>获取或设置文本。</summary>
public string this[string key]
{
get { return GetText(key); }
set { SetText(key, value); }
}
/// <summary>获取错误信息。</summary>
private Action<string> Error { get; set; }
/// <summary></summary>
~Redis()
{
Dispose(false);
}
/// <summary></summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool disposing)
{
if (disposing)
{
if (socket != null)
{
SendCommand("QUIT");
SendBySuccess();
socket.Close();
socket = null;
}
}
}
/// <summary>连接 Redis 服务。</summary>
public string Connect()
{
try
{
if (socket != null && socket.Connected) return null;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;
socket.SendTimeout = _timeout;
socket.Connect(_host, _port);
if (!socket.Connected)
{
socket.Close();
socket = null;
return "Socket 连接失败。";
}
buffered = new BufferedStream(new NetworkStream(socket), BufferSize);
if (_pass != null) return SendBySuccess("AUTH", _pass);
return null;
}
catch (Exception ex)
{
var error = ex.GetType().Name + ": " + ex.Message;
return error;
}
}
#endregion
#region Static
// byte[] end_data = new byte[] { (byte)'\r', (byte)'\n' };
byte[] CRLF = new byte[] { 13, 10 };
static byte[] ToBytes(string text)
{
if (string.IsNullOrEmpty(text)) return new byte[0];
return Encoding.UTF8.GetBytes(text);
}
static int Length(string text)
{
return ToBytes(text).Length;
}
static string ToText(byte[] bytes)
{
if (bytes != null && bytes.Length > 0)
{
try
{
return Encoding.UTF8.GetString(bytes);
}
catch { }
}
return "";
}
#endregion
#region Common
T OnError<T>(string message, T @return)
{
Error?.Invoke(message);
return @return;
}
object OnError(string message) => OnError<object>(message, null);
string ReadLine()
{
var sb = new StringBuilder();
int c;
while ((c = buffered.ReadByte()) != -1)
{
if (c == '\r') continue;
if (c == '\n') break;
sb.Append((char)c);
}
return sb.ToString();
}
byte[] ReadData()
{
string line = ReadLine();
if (line.Length < 1) return null;
if (line == "$-1") return null;
char flag = line[0];
if (flag == '-') return OnError(line.Substring(1), null as byte[]);
if (flag == '$')
{
int length;
if (!int.TryParse(line.Substring(1), out length)) return null;
var buffer = new byte[length];
int writted = 0;
while (writted < length)
{
int read = buffered.Read(buffer, writted, length - writted);
if (read < 1) return null;
writted += read;
}
if (buffered.ReadByte() != '\r' || buffered.ReadByte() != '\n') return null;
return buffer;
}
return null;
}
bool Send(string head, byte[] data)
{
if (string.IsNullOrEmpty(head)) return false;
if (!string.IsNullOrEmpty(Connect())) return false;
byte[] HeadBytes = ToBytes(head);
try
{
socket.Send(HeadBytes);
if (!head.EndsWith("\r\n")) socket.Send(CRLF);
if (data != null)
{
if (data.Length > 0) socket.Send(data);
socket.Send(CRLF);
}
return true;
}
catch (Exception)
{
// SocketException 已超时。
socket.Close();
socket = null;
return false;
}
}
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentNullException"></exception>
bool SendCommand(byte[] data, string cmd, params object[] args)
{
if (string.IsNullOrEmpty(cmd)) throw new ArgumentException(nameof(cmd));
var argsLength = (args != null || args is object[]) ? args.Length : 0;
argsLength += 1; // cmd
if (data != null) argsLength += 1; // data
var sb = new StringBuilder();
sb.Append("*");
sb.Append(argsLength.ToString());
sb.Append("\r\n");
sb.Append("$");
sb.Append(cmd.Length);
sb.Append("\r\n");
sb.Append(cmd);
sb.Append("\r\n");
if (args != null)
{
foreach (var arg in args)
{
var line = arg == null ? "" : arg.ToString();
sb.Append("$");
sb.Append(Length(line).ToString());
sb.Append("\r\n");
sb.Append(line);
sb.Append("\r\n");
}
}
if (data != null)
{
sb.Append("$");
sb.Append(data.Length.ToString());
sb.Append("\r\n");
}
return Send(sb.ToString(), data);
}
/// <exception cref="ArgumentException"></exception>
/// <exception cref="ArgumentNullException"></exception>
bool SendCommand(string cmd, params object[] args) => SendCommand(null, cmd, args);
string SendBySuccess(string cmd = null, params object[] args)
{
if (!string.IsNullOrEmpty(cmd))
{
if (!SendCommand(cmd, args)) return "连接 Redis 服务器失败。";
}
// 获取错误信息。
int c = buffered.ReadByte();
if (c == -1) return "没有更多字节。";
string s = ReadLine();
if (c == '-') return s.StartsWith("ERR ") ? s.Substring(4) : s;
return null;
}
long SendByInt(byte[] data, string cmd, params object[] args)
{
if (!SendCommand(data, cmd, args)) return 0;
int c = buffered.ReadByte();
if (c == -1) return 0;
string line = ReadLine();
if (c == '-') return 0;
if (c == ':')
{
if (long.TryParse(line, out long value)) return value;
}
return 0;
}
long SendByInt(string cmd, params object[] args) => SendByInt(null, cmd, args);
string SendByString(string cmd, params object[] args)
{
if (!SendCommand(cmd, args)) return null;
int c = buffered.ReadByte();
if (c == -1) return null;
var line = ReadLine();
if (line.Length > 0)
{
if (line[0] == '-') return OnError(line, line);
if (line[0] == '+') return OnError(line, line);
}
return line;
}
byte[] SendByData(string cmd, params object[] args) => SendCommand(cmd, args) ? ReadData() : null;
/// <summary></summary>
byte[][] SendByDataArray(string cmd, params object[] args)
{
if (!SendCommand(cmd, args)) return null;
int c = buffered.ReadByte();
if (c == -1) return null;
string s = ReadLine();
if (c == '-') return OnError(s, null as byte[][]);
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;
}
/// <summary></summary>
string[] SendByStringArray(string cmd, params object[] args)
{
byte[][] reply = SendByDataArray(cmd, args);
if (reply == null) return null;
string[] keys = new string[reply.Length];
for (int i = 0; i < reply.Length; i++) keys[i] = ToText(reply[i]);
return keys;
}
#endregion
#region DB
/// <summary>获取服务器信息。</summary>
public Dictionary<string, string> Info()
{
byte[] r = SendByData("INFO");
var dict = new Dictionary<string, string>();
foreach (var line in ToText(r).Split('\n'))
{
int p = line.IndexOf(':');
if (p < 0) continue;
dict.Add(line.Substring(0, p), line.Substring(p + 1));
}
return dict;
}
/// <summary>选择数据库,默认为 0。</summary>
/// <returns>错误信息。</returns>
public string SelectDB(int db = 0)
{
_db = db;
return SendBySuccess("SELECT", _db);
}
/// <summary>清空 DB。</summary>
/// <returns>错误信息。</returns>
public string Flush(bool allDB = false)
{
return allDB ? SendBySuccess("FLUSHALL") : SendBySuccess("FLUSHDB");
}
/// <summary>执行同步保存操作,将所有数据的快照以 RDB 文件的形式保存到磁盘上。</summary>
/// <returns>错误信息。</returns>
public string Save(bool background = true)
{
return background ? SendBySuccess("BGSAVE") : SendBySuccess("SAVE");
}
/// <summary>以 UNIX 时间戳格式返回最近一次 Redis 成功将数据保存到磁盘上的时间。</summary>
public DateTime LastSave()
{
const long UnixEpoch = 621355968000000000L;
long t = SendByInt("LASTSAVE");
return new DateTime(UnixEpoch) + TimeSpan.FromSeconds(t);
}
/// <summary>断开所有客户端,并关闭 Redis 服务进程。</summary>
public void Shutdown()
{
SendCommand("SHUTDOWN");
try
{
string s = ReadLine(); // 服务器可能会返回错误。
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;
}
}
#endregion
#region Key
/// <summary>获取 DB 中 Keys 的数量。</summary>
/// <remarks>执行 DBSIZE 命令。</remarks>
public long CountKeys()
{
return SendByInt("DBSIZE");
}
/// <summary>列出 Keys。</summary>
/// <remarks>执行 KEYS 命令。</remarks>
/// <returns>发生错误时返回 NULL 值。</returns>
public string[] ListKeys(string pattern = "*")
{
var p = string.IsNullOrEmpty(pattern) ? "*" : pattern;
return SendByStringArray("KEYS", p);
}
/// <summary>包含指定 Key。</summary>
public bool ContainsKey(string key)
{
if (key == null) return false;
return SendByInt("EXISTS", key) == 1;
}
/// <summary>删除指定 Key。</summary>
public bool Delete(string key)
{
if (key == null) return false; // throw new ArgumentNullException("key");
return SendByInt("DEL", key) == 1;
}
/// <summary>删除指定的多个 Key。不存在的 key 会被忽略。</summary>
/// <returns>删除的 Key 的数量。</returns>
public long Delete(params string[] keys)
{
if (keys == null || keys.Length < 1) return 0;
return SendByInt("DEL", keys);
}
/// <summary>获取 Key 的值类型,正确类型为 none | string | set | list。</summary>
public string TypeOf(string key)
{
if (key == null) return "";
return SendByString("TYPE", key);
}
/// <summary>随机返回一个已有的 Key。</summary>
public string RandomKey()
{
return SendByString("RANDOMKEY");
}
/// <summary>对 Key 重命名。</summary>
/// <remarks>已经存在的 newKey 会被覆盖。</remarks>
public bool Rename(string oldKey, string newKey)
{
// Exceptions
if (string.IsNullOrEmpty(oldKey)) return false;
if (string.IsNullOrEmpty(newKey)) return false;
return SendByString("RENAME", oldKey, newKey)[0] == '+';
}
/// <summary>设置 Key 的过期时间,过期后 Key 会被自动删除。</summary>
/// <remarks>在 Redis 2.1.3 之前的版本中,修改已经设置过生存时间的 Key,将会和删除 Key 有同样的效果。</remarks>
public bool Expire(string key, int seconds)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return false;
if (seconds < 1) return false;
return SendByInt("EXPIRE", key, seconds) == 1L;
}
/// <summary>设置 Key 的过期时间,过期后 Key 会被自动删除。</summary>
public bool ExpireAt(string key, DateTime time)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return false;
var stamp = Convert.ToInt64((time - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds);
return SendByInt("EXPIREAT", key, stamp) == 1L;
}
/// <summary>获取 Key 过期前的剩余时间。</summary>
/// <remarks>秒数。当 Key 不存在时返回 -1 值。</remarks>
public long TimeToLive(string key)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return -1;
return SendByInt("TTL", key);
}
#endregion
#region Bytes 字节数组
/// <summary>获取值。Key 无效时获取 NULL 值。</summary>
public byte[] GetBytes(string key)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return null;
return SendByData("GET", key);
}
/// <summary>获取多个 Key 值。</summary>
/// <returns>参数无效时返回 NULL 值。</returns>
public byte[][] GetMultipleBytes(params string[] keys)
{
// Exceptions
if (keys == null || keys.Length == 0) return null;
return SendByDataArray("MGET", keys);
}
/// <summary>设置字节数组。</summary>
/// <returns>错误信息。</returns>
public string SetBytes(string key, byte[] bytes, bool replace = true)
{
// Exceptions
if (key == null) return "Key 无效。";
if (bytes == null) return "Bytes 无效";
if (bytes.Length > MaxLength) return "Value[] 长度超出限制。";
var cmd = replace ? "SET" : "SETNX";
var success = SendCommand(bytes, cmd, key);
if (!success) return "设置失败。";
return SendBySuccess();
}
/// <summary>设置多个值。</summary>
/// <returns>错误信息。</returns>
public string SetBytes(IEnumerable<KeyValuePair<string, byte[]>> pairs)
{
// Exceptions
if (pairs == null) return "键值对无效。";
var bytes = null as byte[];
var keys = new List<string>();
using (var memory = new MemoryStream())
{
foreach (var pair in pairs)
{
if (string.IsNullOrEmpty(pair.Key)) continue;
if (keys.Contains(pair.Key)) continue;
var keyBytes = ToBytes(pair.Key);
var keyLength = ToBytes("$" + keyBytes.Length.ToString() + "\r\n");
memory.Write(keyLength, 0, keyLength.Length);
memory.Write(keyBytes, 0, keyBytes.Length);
memory.Write(CRLF, 0, CRLF.Length);
var valueBytes = pair.Value ?? new byte[0];
var valueLength = ToBytes("$" + valueBytes.Length + "\r\n");
memory.Write(valueLength, 0, valueLength.Length);
memory.Write(valueBytes, 0, valueBytes.Length);
memory.Write(CRLF, 0, CRLF.Length);
keys.Add(pair.Key);
}
if (keys.Count > 0) bytes = memory.ToArray();
}
if (keys.Count < 1 || bytes == null || bytes.Length < 1) return "无法生成指令。";
var head = "*" + (keys.Count * 2 + 1).ToString() + "\r\n$4\r\nMSET\r\n";
var sent = Send(head, bytes);
if (!sent) return "发送指令失败。";
return SendBySuccess();
}
#endregion
#region Text
/// <summary>获取值。Key 无效时获取 NULL 值。</summary>
public string GetText(string key)
{
// Exceptions
if (key == null) return null;
var bytes = GetBytes(key);
if (bytes == null) return null;
return ToText(bytes);
}
/// <summary>设置 UTF-8 文本。返回错误信息。</summary>
public string SetText(string key, string value, bool replace = true)
{
// Exceptions
if (key == null) return "Key 无效。";
if (value == null) return "Value 无效";
var bytes = value.Length < 1 ? new byte[0] : ToBytes(value);
return SetBytes(key, bytes, replace);
}
/// <summary>设置多个值。返回错误信息。</summary>
public string SetText(IEnumerable<KeyValuePair<string, string>> pairs)
{
// Exceptions
if (pairs == null) return "键值对无效。";
var list = new List<KeyValuePair<string, byte[]>>();
foreach (var pair in pairs)
{
var value = ToBytes(pair.Value);
list.Add(new KeyValuePair<string, byte[]>(pair.Key, value));
}
return SetBytes(list);
}
#endregion
#region Value 数值
/// <summary>增大 Key 中储存的数值,当 Key 不存在,则先初始化为 0,再执行操作。</summary>
/// <remarks>参数 By 为 1 时执行 INCR,否则执行 INCRBY。</remarks>
/// <returns>执行操作后的值。发生错误时返回 0 值。</returns>
public long Increment(string key, long by = 1)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return 0;
if (by == 1) return SendByInt("INCR", key);
else return SendByInt("INCRBY", key, by);
}
/// <summary>减小 Key 中储存的数值,当 Key 不存在,则先初始化为 0,再执行操作。</summary>
/// <remarks>参数 By 为 1 时执行 DECR,否则执行 DECRBY。</remarks>
/// <returns>执行操作后的值。发生错误时返回 0 值。</returns>
public long Decrement(string key, long by = 1)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return 0;
if (by == 1) return SendByInt("DECR", key);
return SendByInt("DECRBY", key, by);
}
#endregion
#region Set 集合。
/// <summary>返回 list, set 或 sorted set 中的元素。</summary>
/// <returns>排序后的元素列表。</returns>
public byte[][] Sort(string key, int skip = 0, int count = 0, bool alpha = false, bool desc = false, string by = null, string get = null)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return null;
var array = new ArrayList();
array.Add(key);
if (skip > 0 || count > 0)
{
array.Add("LIMIT");
array.Add(skip > 0 ? skip : 0);
array.Add(count > 0 ? count : 0);
}
if (alpha) array.Add("ALPHA");
if (desc) array.Add("DESC");
if (!string.IsNullOrEmpty(by))
{
array.Add("BY");
array.Add(by);
}
if (!string.IsNullOrEmpty(get))
{
array.Add("GET");
array.Add(get);
}
var options = array.ToArray();
return SendByDataArray("SORT", options);
}
/// <summary>存储 list, set 或 sorted set 中的元素。</summary>
/// <returns>存储在目的列表中元素个数。</returns>
public long Sort(string key, string store, int skip = 0, int count = 0, bool alpha = false, bool desc = false, string by = null, string get = null)
{
// Exceptions
if (string.IsNullOrEmpty(key)) return -1;
if (string.IsNullOrEmpty(store)) return -1;
var array = new ArrayList();
array.Add(key);
array.Add("STORE");
array.Add(store);
if (skip > 0 || count > 0)
{
array.Add("LIMIT");
array.Add(skip > 0 ? skip : 0);
array.Add(count > 0 ? count : 0);
}
if (alpha) array.Add("ALPHA");
if (desc) array.Add("DESC");
if (!string.IsNullOrEmpty(by))
{
array.Add("BY");
array.Add(by);
}
if (!string.IsNullOrEmpty(get))
{
array.Add("GET");
array.Add(get);
}
var options = array.ToArray();
return SendByInt("SORT", options);
}
#endregion
}
}