.net core实现redisClient
引言
最近工作上有需要使用redis,于是便心血来潮打算自己写一个C#客户端。经过几天的努力,目前该客户端已经基本成型,下面简单介绍一下。
通信协议
要想自行实现redisClient,则必须先要了解Redis的socket能信协议。新版统一请求协议在 Redis 1.2 版本中引入, 并最终在 Redis 2.0 版本成为 Redis 服务器通信的标准方式。在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。
以下是这个协议的一般形式:
*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF
注:命令本身也作为协议的其中一个参数来发送。举个例子, 以下是一个命令协议的打印版本:
*
$
SET
$
mykey
$
myvalue
这个命令的实际协议值如下:
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
稍后看到, 这种格式除了用作命令请求协议之外, 也用在命令的回复协议中: 这种只有一个参数的回复格式被称为批量回复(Bulk Reply)。统一协议请求原本是用在回复协议中, 用于将列表的多个项返回给客户端的, 这种回复格式被称为多条批量回复(Multi Bulk Reply)。一个多条批量回复以 *<argc>\r\n 为前缀, 后跟多条不同的批量回复, 其中 argc 为这些批量回复的数量。
Redis 命令会返回多种不同类型的回复。一个状态回复(或者单行回复,single line reply)是一段以 "+" 开始、 "\r\n" 结尾的单行字符串。通过检查服务器发回数据的第一个字节, 可以确定这个回复是什么类型:
- 状态回复(status reply)的第一个字节是 "+"
- 错误回复(error reply)的第一个字节是 "-"
- 整数回复(integer reply)的第一个字节是 ":"
- 批量回复(bulk reply)的第一个字节是 "$"
- 多条批量回复(multi bulk reply)的第一个字节是 "*"
.net Core Socket
说起socket,就不得不说IOCP了,这个方案本身就是为了解决多连接、高并发而设计的;但是话又说回来,任何方案都有局限性,不可能解决所有问题;这里不去讨论用在这里是否合适,反正本人就是想这么试一把:用一个简单的ioc模式实现SAEA.Socket,并为此设定各种场景,反过来优化SAEA.Socket本身。下面是一段服务器接收连接的代码:
private void ProcessAccept(SocketAsyncEventArgs args)
{
if (args == null)
{
args = new SocketAsyncEventArgs();
args.Completed += ProcessAccepted;
}
else
{
args.AcceptSocket = null;
}
if (!_listener.AcceptAsync(args))
{
ProcessAccepted(_listener, args);
}
}
项目结构
在网上找到redis的命令文档后,本人觉的准备工作差不多了,可以初步定一下项目结构:
Core:定义的是redisclient相关最基本的业务
Interface:定义的是一些需要抽象出来的接口
Model:定义的是redis的数据模型及其请求、回复的类型枚举
Net:这里就是将继承实现SAEA.Socket而来的RedisConnection通信基础
命令解码器
通过前面的准备工作了解到redisClient的关键在于命令的编解码,至于高大上算法或redis官方算法的实现,本人没有去详细了解,一冲动就自行实现了自定义版的解码器。
public string Coder(RequestType commandName, params string[] @params)
{
_autoResetEvent.WaitOne();
_commandName = commandName;
var sb = new StringBuilder();
sb.AppendLine("*" + @params.Length);
foreach (var param in @params)
{
sb.AppendLine("$" + param.Length);
sb.AppendLine(param);
}
return sb.ToString();
}
public ResponseData Decoder()
{
var result = new ResponseData(); string command = null; string error = null; var len = ; switch (_commandName)
{
case RequestType.PING:
command = BlockDequeue();
if (GetStatus(command, out error))
{
result.Type = ResponseType.OK;
result.Data = "PONG";
}
else
{
result.Type = ResponseType.Error;
result.Data = error;
}
break;
case RequestType.AUTH:
case RequestType.SELECT:
case RequestType.SLAVEOF:
case RequestType.SET:
case RequestType.DEL:
case RequestType.HSET:
case RequestType.HDEL:
case RequestType.LSET:
command = BlockDequeue();
if (GetStatus(command, out error))
{
result.Type = ResponseType.OK;
result.Data = "OK";
}
else
{
result.Type = ResponseType.Error;
result.Data = error;
}
break;
case RequestType.TYPE:
command = BlockDequeue();
if (GetStatusString(command, out string msg))
{
result.Type = ResponseType.OK;
}
else
{
result.Type = ResponseType.Error;
}
result.Data = msg;
break;
case RequestType.GET:
case RequestType.GETSET:
case RequestType.HGET:
case RequestType.LPOP:
case RequestType.RPOP:
case RequestType.SRANDMEMBER:
case RequestType.SPOP:
len = GetWordsNum(BlockDequeue(), out error);
if (len == -)
{
result.Type = ResponseType.Empty;
result.Data = error;
}
else
{
result.Type = ResponseType.String;
result.Data += BlockDequeue();
}
break;
case RequestType.KEYS:
case RequestType.HKEYS:
case RequestType.LRANGE:
case RequestType.SMEMBERS:
result.Type = ResponseType.Lines;
var sb = new StringBuilder();
var rn = GetRowNum(BlockDequeue(), out error);
if (!string.IsNullOrEmpty(error))
{
result.Type = ResponseType.Error;
result.Data = error;
break;
}
//再尝试读取一次,发现有回车行出现
if (rn == -) rn = GetRowNum(BlockDequeue(), out error);
if (!string.IsNullOrEmpty(error))
{
result.Type = ResponseType.Error;
result.Data = error;
break;
}
if (rn > )
{
for (int i = ; i < rn; i++)
{
len = GetWordsNum(BlockDequeue(), out error);
sb.AppendLine(BlockDequeue());
}
}
result.Data = sb.ToString();
break;
case RequestType.HGETALL:
case RequestType.ZRANGE:
case RequestType.ZREVRANGE:
result.Type = ResponseType.KeyValues;
sb = new StringBuilder();
rn = GetRowNum(BlockDequeue(), out error);
if (!string.IsNullOrEmpty(error))
{
result.Type = ResponseType.Error;
result.Data = error;
break;
}
if (rn > )
{
for (int i = ; i < rn; i++)
{
len = GetWordsNum(BlockDequeue(), out error);
sb.AppendLine(BlockDequeue());
}
}
result.Data = sb.ToString();
break;
case RequestType.DBSIZE:
case RequestType.EXISTS:
case RequestType.EXPIRE:
case RequestType.PERSIST:
case RequestType.SETNX:
case RequestType.HEXISTS:
case RequestType.HLEN:
case RequestType.LLEN:
case RequestType.LPUSH:
case RequestType.RPUSH:
case RequestType.LREM:
case RequestType.SADD:
case RequestType.SCARD:
case RequestType.SISMEMBER:
case RequestType.SREM:
case RequestType.ZADD:
case RequestType.ZCARD:
case RequestType.ZCOUNT:
case RequestType.ZREM:
case RequestType.PUBLISH:
var val = GetValue(BlockDequeue(), out error);
if (!string.IsNullOrEmpty(error))
{
result.Type = ResponseType.Error;
result.Data = error;
break;
}
if (val == )
{
result.Type = ResponseType.Empty;
}
else
{
result.Type = ResponseType.OK;
}
result.Data = val.ToString();
break;
case RequestType.INFO:
var rnum = GetWordsNum(BlockDequeue(), out error);
if (!string.IsNullOrEmpty(error))
{
result.Type = ResponseType.Error;
result.Data = error;
break;
}
var info = "";
while (info.Length < rnum)
{
info += BlockDequeue();
}
result.Type = ResponseType.String;
result.Data = info;
break;
case RequestType.SUBSCRIBE:
var r = "";
while (IsSubed)
{
r = BlockDequeue();
if (r == "message\r\n")
{
result.Type = ResponseType.Sub;
BlockDequeue();
result.Data = BlockDequeue();
BlockDequeue();
result.Data += BlockDequeue();
break;
}
}
break;
case RequestType.UNSUBSCRIBE:
var rNum = GetRowNum(BlockDequeue(), out error);
var wNum = GetWordsNum(BlockDequeue(), out error);
BlockDequeue();
wNum = GetWordsNum(BlockDequeue(), out error);
var channel = BlockDequeue();
var vNum = GetValue(BlockDequeue(), out error);
IsSubed = false;
break;
}
_autoResetEvent.Set();
return result;
}
命令的封装与测试
有了socket、redisCoder之后,现在就可以按照官方的redis命令来进行.net core的封装了。本人将这些操作封装到RedisClient、RedisDataBase两个类中,然后又想到连接复用的问题,简单实现了一个连接池RedisClientFactory的类。这样一来就可以好好的来实验一把,看看之前的设想最终能不能实现了:
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.RedisSocketTest
*文件名: Program
*版本号: V1.0.0.0
*唯一标识:3d4f939c-3fb9-40e9-a0e0-c7ec773539ae
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/3/17 10:37:15
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/3/19 10:37:15
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.Commom;
using SAEA.RedisSocket;
using System; namespace SAEA.RedisSocketTest
{
class Program
{
static void Main(string[] args)
{
ConsoleHelper.Title = "SAEA.RedisSocketTest";
ConsoleHelper.WriteLine("输入ip:port连接RedisServer"); var ipPort = ConsoleHelper.ReadLine();
if (string.IsNullOrEmpty(ipPort))
{
ipPort = "127.0.0.1:6379";
}
RedisClient redisClient = new RedisClient(ipPort);
redisClient.Connect();
//redisClient.Connect("wenli"); var info = redisClient.Info();
if (info.Contains("NOAUTH Authentication required."))
{
while (true)
{
ConsoleHelper.WriteLine("请输入redis连接密码");
var auth = ConsoleHelper.ReadLine();
if (string.IsNullOrEmpty(auth))
{
auth = "yswenli";
}
var a = redisClient.Auth(auth);
if (a.Contains("OK"))
{
break;
}
else
{
ConsoleHelper.WriteLine(a);
}
}
} //redisConnection.SlaveOf(); //redisConnection.Ping(); redisClient.Select(); //ConsoleHelper.WriteLine(redisConnection.Type("key0")); ConsoleHelper.WriteLine("dbSize:{0}", redisClient.DBSize().ToString()); RedisOperationTest(redisClient, true);
ConsoleHelper.ReadLine();
} private static void RedisOperationTest(object sender, bool status)
{
RedisClient redisClient = (RedisClient)sender;
if (status)
{
ConsoleHelper.WriteLine("连接redis服务器成功!"); #region key value ConsoleHelper.WriteLine("回车开始kv插值操作...");
ConsoleHelper.ReadLine();
for (int i = ; i < ; i++)
{
redisClient.GetDataBase().Set("key" + i, "val" + i);
}
//redisConnection.GetDataBase().Exists("key0");
ConsoleHelper.WriteLine("kv插入完成..."); ConsoleHelper.WriteLine("回车开始获取kv值操作...");
ConsoleHelper.ReadLine(); var keys = redisClient.GetDataBase().Keys().Data.ToArray(false, "\r\n"); foreach (var key in keys)
{
var val = redisClient.GetDataBase().Get(key);
ConsoleHelper.WriteLine("Get val:" + val);
}
ConsoleHelper.WriteLine("获取kv值完成..."); ConsoleHelper.WriteLine("回车开始开始kv移除操作...");
ConsoleHelper.ReadLine();
foreach (var key in keys)
{
redisClient.GetDataBase().Del(key);
}
ConsoleHelper.WriteLine("移除kv值完成...");
#endregion #region hashset
string hid = "wenli"; ConsoleHelper.WriteLine("回车开始HashSet插值操作...");
ConsoleHelper.ReadLine();
for (int i = ; i < ; i++)
{
redisClient.GetDataBase().HSet(hid, "key" + i, "val" + i);
}
ConsoleHelper.WriteLine("HashSet插值完成..."); ConsoleHelper.WriteLine("回车开始HashSet插值操作...");
ConsoleHelper.ReadLine();
var hkeys = redisClient.GetDataBase().GetHKeys(hid).Data.ToArray();
foreach (var hkey in hkeys)
{
var val = redisClient.GetDataBase().HGet(hid, hkey);
ConsoleHelper.WriteLine("HGet val:" + val.Data);
} var hall = redisClient.GetDataBase().HGetAll("wenli");
ConsoleHelper.WriteLine("HashSet查询完成..."); ConsoleHelper.WriteLine("回车开始HashSet移除操作...");
ConsoleHelper.ReadLine();
foreach (var hkey in hkeys)
{
redisClient.GetDataBase().HDel(hid, hkey);
}
ConsoleHelper.WriteLine("HashSet移除完成..."); #endregion //redisConnection.GetDataBase().Suscribe((c, m) =>
//{
// ConsoleHelper.WriteLine("channel:{0} msg:{1}", c, m);
// redisConnection.GetDataBase().UNSUBSCRIBE(c);
//}, "c39654"); ConsoleHelper.WriteLine("测试完成!");
}
else
{
ConsoleHelper.WriteLine("连接失败!");
}
}
}
}
经过上面的代码测试,使用redis-cli工具进行monitor命令监控发现——搞定了!另外源码本人已发到github上面了,SAEA.RedisSocket的详细可查看:https://github.com/yswenli/SAEA/tree/master/Src/SAEA.RedisSocket
转载请标明本文来源:http://www.cnblogs.com/yswenli/p/8608661.html
更多内容欢迎star作者的github:https://github.com/yswenli/SAEA
如果发现本文有什么问题和任何建议,也随时欢迎交流~
.net core实现redisClient的更多相关文章
- 下一个计划 : .NET/.NET Core应用性能管理系统
前言 最近几个月一直在研究开源的APM和监控方案,并对比使用了Zipkin,CAT,Sky-walking,PinPoint(仅对比,未实际部署),Elastic APM,TICK Stack,Pro ...
- Linux环境下发布.net core
一.安装Linux环境 1. 安装VM虚拟机和操作系统 VM虚拟工具安装的过程详见:http://blog.csdn.net/stpeace/article/details/78598333.直接按照 ...
- Redis系列文章总结:ASP.Net Core 中如何借助CSRedis实现一个安全高效的分布式锁
引言:最近回头看了看开发的.Net Core 2.1项目的复盘总结,其中在多处用到Redis实现的分布式锁,虽然在OnResultExecuting方法中做了防止死锁的处理,但在某些场景下还是会发生死 ...
- Java RedisClient
package org.rx.util; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import or ...
- [翻译] C# 8.0 新特性 Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南) 【由浅至深】redis 实现发布订阅的几种方式 .NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐
[翻译] C# 8.0 新特性 2018-11-13 17:04 by Rwing, 1179 阅读, 24 评论, 收藏, 编辑 原文: Building C# 8.0[译注:原文主标题如此,但内容 ...
- .net core 使用redis 基于 StackExchange.Redis
一.添加引用包 StackExchange.Redis Microsoft.Extensions.Configuration 二.修改配置文件 appsettings.json { " ...
- c#实例化继承类,必须对被继承类的程序集做引用 .net core Redis分布式缓存客户端实现逻辑分析及示例demo 数据库笔记之索引和事务 centos 7下安装python 3.6笔记 你大波哥~ C#开源框架(转载) JSON C# Class Generator ---由json字符串生成C#实体类的工具
c#实例化继承类,必须对被继承类的程序集做引用 0x00 问题 类型“Model.NewModel”在未被引用的程序集中定义.必须添加对程序集“Model, Version=1.0.0.0, Cu ...
- 小白开学Asp.Net Core 《四》
小白开学Asp.Net Core<三> —— 使用AspectCore-Framework 一.AspectCore-Frame ...
- 三分钟学会Redis在.NET Core中做缓存中间件
大家好,今天给大家说明如何在.NET Core中使用Redis,我们在想要辩论程序的好与坏,都想需要一个可视化工具,我经常使用的是一位国内大牛开发的免费工具,其Github地址为: https://g ...
随机推荐
- json字符串转换成json对象,json对象转换成字符串,值转换成字符串,字符串转成值
一.json相关概念 json,全称为javascript object notation,是一种轻量级的数据交互格式.采用完全独立于语言的文本格式,是一种理想的数据交换格式. 同时,json是jav ...
- Spring 框架系列之 JDBC 整合实例
微信公众号:compassblog 欢迎关注.转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1.Spring框架整合 DAO 模板 JDBC:org.springframework.jdb ...
- chrome使用Timeline做性能分析
使用Timeline做性能分析 Timeline面板记录和分析了web应用运行时的所有活动情况,这是研究和查找性能问题的最佳途径.###Timeline面板概览 Timeline面板主要有三个部分构成 ...
- linux 集群及lvs
集群及LVS 集群: 一组通过高速网络互联的计算机组,并以单一系统的模式加以管理 价格很多服务器集中起来,提供同一种服务,在客户端看起来就像只有一个服务器 可以在付出较低成本的情况下获得在性能,可靠性 ...
- javascript获取网页地址栏的id
//获取网页的uid (function ($) { $.getUrlParam = function (name) { var reg = new RegExp("(^|&)&qu ...
- SQL注入攻击三部曲之高级篇
SQL注入攻击三部曲之高级篇 经过了入门篇和进阶篇的学习,相信诸位想要破解一般的网站是没有什么问题了,但是先别得意.正所谓学海无涯,技术的进步也是没有止境的.SQL注入是一个看起来简单,但是变数很多的 ...
- Java Web项目(Extjs)报错五
1. Java Web项目(Extjs)报错五 具体报错如下: usage: java org.apache.catalina.startup.Catalina [ -config {pathname ...
- C#连接oracle数据库步骤
1. 确认操作系统类型,操作系统是64位还是32位: 2. 按对应版本安装oralce客户端版本(64位还是32位): 3. 安装oralce管理员模块,同时赋予安装目录权限 4. 注册old ...
- span是没有value标签的,要向获得标签内部的值改怎么办。
1,js实现 var div = document.getElementById('divId');var spans = div.getElementsByTagName('span');var s ...
- Java冒泡排序法升级版
/* * 冒泡排序之升级版,可比较整型数组.小数型数组 * * */ public static <T extends Comparable<T>> void Bubb ...