零:Demo 跑出来的结果如图
上图说明
图中左边蓝色的命令行界面,是用windows powershell 命令行链接的。
1.打开powershell命令行界面,输入命令【telnet 127.0.0.1 6379】。
如果没有powershell,使用cmd 命令行界面也是可以达到测试redis 命令的效果的。
输入PING 命令,redis 接收到,它将返回一个PONG字符串。命令的作用通常是测试与服务器的连接是否仍然生效。PING命令
输入Info 命令,redis 会返回一大串的redis 服务端的信息。这个命令,主要用来测试拆包的情况,下面会讲到拆包如何处理。
图中右边黑色的命令行界面,是Demo 跑出来的控制台应用程序。
两个结果一对比,测试出来,我们的Demo已经得到了正确的结果。
Ok,下面开始进入正戏。
一 DotNetty 是什么
DotNetty 是netty 一个C#版本。
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。【摘自百度百科】
笔者认为 Netty是Java生态圈的一个重要组件。
原生Socket编程,学习成本高,使用原生的Socket做项目,那就是开着一辆绿皮火车,动次打次。。。。
使用Netty,开做项目,那开发效率无疑是高铁般的存在。
而且使用原生的socket 编程是很困难的
二,写这个Demo 的起因
学习DotNetty很久。从DotNetty 0.4版本。到现在的0.48版本。自己实现一个C/S端的例子。还没有太好的想法去实现。
今天看到haifeiWu 的高作《Netty 源码中对 Redis 协议的实现》,遂想跟着实现一个。
所以,才有了今天的Demo.
是的,它还只是一个Demo.并不能取代StackExchange.Redis。
三,了解一下redis的协议
RESP 是 Redis 序列化协议的简写。它是一种直观的文本协议,优势在于实现非常简单,解析性能极好。
Redis 协议将传输的结构数据分为 5 种最小单元类型,单元结束时统一加上回车换行符号\r\n,来表示该单元的结束。
单行字符串 以 + 符号开头。
多行字符串 以 $ 符号开头,后跟字符串长度。
整数值 以 : 符号开头,后跟整数的字符串形式。
错误消息 以 - 符号开头。
数组 以 * 号开头,后跟数组的长度。
关于 RESP 协议的具体介绍感兴趣的小伙伴请移步 haifeiWu 的另一篇文章Redis协议规范(译文)
以上第二点是摘抄自 haifeiWu中的介绍
四 Demo 代码
1,定义枚举 RedisMessageType
1 internal enum RedisMessageType:byte 2 ????{ 3 ????????/// <summary> 4 ????????/// 以 + 开头的单行字符串 5 ????????/// </summary> 6 ????????SimpleString = 43, 7 ?8 ????????/// <summary> 9 ????????/// ?以 - 开头的错误信息10 ????????/// </summary>11 ????????Error = 45,12 ????????/// <summary>13 ????????/// 以 : 开头的整型数据INTEGER14 ????????/// </summary>15 ????????Integer = 58,16 ????????/// <summary>17 ????????/// 以 $ 开头的多行字符串18 ????????/// </summary>19 ????????BulkString = 36,20 21 ????????/// <summary>22 ????????/// 以 * 开头的数组23 ????????/// </summary>24 ????????ArrayHeader = 4225 ????}
2,定义RedisObject 并定义了虚拟的方法 WriteBuffer
1 public class RedisObject 2 ????{ 3 ????????public virtual void WriteBuffer(IByteBuffer output) 4 ????????{ 5 ????????} 6 ????} 7 ?8 public class RedisCommon : RedisObject 9 ????{10 ????????public RedisCommon()11 ????????{12 ????????????Commond = new List<string>();13 ????????}14 ????????public List<string> Commond { get; set; }15 ????????public override void WriteBuffer(IByteBuffer output)16 ????????{17 ????????????//请求头部格式, *<number of arguments>\r\n18 ????????????//const string headstr = "*{0}\r\n";19 ????????????//参数信息 ??????$<number of bytes of argument N>\r\n<argument data>\r\n20 ????????????//const string bulkstr = "${0}\r\n{1}\r\n";21 ????????????StringBuilder stringBuilder = new StringBuilder();22 ????????????stringBuilder.AppendFormat("*{0}\r\n",Commond.Count);23 ????????????foreach (var item in Commond)24 ????????????{25 ????????????????stringBuilder.AppendFormat("${0}\r\n{1}\r\n",item.Length,item);26 ????????????}27 ????????????//*1\r\n$4\r\nPING\r\n28 ????????????byte[] bytes = Encoding.UTF8.GetBytes(stringBuilder.ToString());29 ????????????output.WriteBytes(bytes);30 ????????}31 ????}
3,定义RedisEncoder 编码器, 它集成了MessageToByteEncoder<T>方法。主要是将RedisObject,写到IByteBuffer里面。
public class RedisEncoder:DotNetty.Codecs.MessageToByteEncoder<RedisObject> ???{ ???????protected override void Encode(IChannelHandlerContext context, RedisObject message, IByteBuffer output) ???????{ ???????????message.WriteBuffer(output); ???????????//context.WriteAndFlushAsync(output); ???????} ???}
4,定义 RedisDecoder 解码器,它继承了 ByteToMessageDecoder。
ByteToMessageDecoder 是需要自己实现解决粘包,拆包的。比较低级别,但是灵活。
DotNetty 还有其他比较高级的解码器。
比如 MessageToMessageDecoder, DatagramPacketDecoder,LengthFieldBasedFrameDecoder,LineBasedFrameDecoder,ReplayingDecoder,DelimiterBasedFrameDecoder,StringDecoder。
在李林锋老师的《Netty权威指南》一书中,都能学习到。
通过测试,我们知道了info 命令返回的是一个多行字符串
以 $ 符号开头,后跟字符串长度。假设redis 服务端要返回一个多行字符串,它的返回格式为: ${字符串长度}\r\n{字符串}\r\n
解析多行字符串的代码为
???????private string ReadMultiLine(IByteBuffer input) ???????{ ???????????Int64 strLength = ReadInteger(input); ???????????Int64 packLength = input.ReaderIndex + strLength + 2; ???????????//包的长度,比实际包还要大,跳过他,防止堆积 ???????????if ( input.WriterIndex> packLength) ???????????{ ???????????????input.SkipBytes(input.ReadableBytes); ???????????} ???????????if (strLength == -1) ???????????{ ???????????????return null; ???????????} ???????????//包的长度,比实际包还小 拆包 ???????????if (packLength > input.WriterIndex) ???????????{ ???????????????throw new Exception(""); ???????????} ???????????int count = 0; ???????????int whildCount = 0; ???????????StringBuilder stringBuilder = new StringBuilder(); ???????????while (input.IsReadable()) ???????????{ ???????????????string str= this.ReadString(input); ???????????????count += str.Length; ???????????????stringBuilder.AppendLine(str); ???????????????whildCount++; ???????????}
return stringBuilder.ToString(); ???????}
6.定义 RedisHandle Handler ,他继承了SimpleChannelInboundHandler 的方法。用来接收解码器之后解出来的RedisObJect对象。
public class RedisHandle : SimpleChannelInboundHandler<RedisObject> ???{ ???????protected override void ChannelRead0(IChannelHandlerContext ctx, RedisObject msg) ???????{ ???????????if (msg is ReidsString) ???????????{ ???????????????ReidsString reidsString = (ReidsString)msg; ???????????????Console.WriteLine(reidsString.Content); ???????????} ???????} ???}
结语:附上源码地址
https://gitee.com/hesson/Dotnetty.Redis.Demo
感谢 @蛀牙 对本文的审阅,并提出修改的建议
使用 DotNetty 实现 Redis 的一个控制台应用程序
原文地址:https://www.cnblogs.com/ruxia/p/9477389.html