零: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

 internal enum RedisMessageType:byte
{
/// <summary>
/// 以 + 开头的单行字符串
/// </summary>
SimpleString = , /// <summary>
/// 以 - 开头的错误信息
/// </summary>
Error = ,
/// <summary>
/// 以 : 开头的整型数据INTEGER
/// </summary>
Integer = ,
/// <summary>
/// 以 $ 开头的多行字符串
/// </summary>
BulkString = , /// <summary>
/// 以 * 开头的数组
/// </summary>
ArrayHeader =
}

2,定义RedisObject   并定义了虚拟的方法 WriteBuffer

 public class RedisObject
{
public virtual void WriteBuffer(IByteBuffer output)
{
}
} public class RedisCommon : RedisObject
{
public RedisCommon()
{
Commond = new List<string>();
}
public List<string> Commond { get; set; }
public override void WriteBuffer(IByteBuffer output)
{
//请求头部格式, *<number of arguments>\r\n
//const string headstr = "*{0}\r\n";
//参数信息 $<number of bytes of argument N>\r\n<argument data>\r\n
//const string bulkstr = "${0}\r\n{1}\r\n";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendFormat("*{0}\r\n",Commond.Count);
foreach (var item in Commond)
{
stringBuilder.AppendFormat("${0}\r\n{1}\r\n",item.Length,item);
}
//*1\r\n$4\r\nPING\r\n
byte[] bytes = Encoding.UTF8.GetBytes(stringBuilder.ToString());
output.WriteBytes(bytes);
}
}

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 + ;
//包的长度,比实际包还要大,跳过他,防止堆积
if ( input.WriterIndex> packLength)
{
input.SkipBytes(input.ReadableBytes);
}
if (strLength == -)
{
return null;
}
//包的长度,比实际包还小 拆包
if (packLength > input.WriterIndex)
{
throw new Exception("");
}
int count = ;
int whildCount = ;
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

感谢 @蛀牙 对本文的审阅,并提出修改的建议

最新文章

  1. java调用CXF WebService接口的两种方式
  2. Lua Coroutine详解
  3. Canvas 实现图片剪切
  4. css 常用样式
  5. Silverlight开源项目与第三方控件收集
  6. [Hibernate] - Annotations - One To One
  7. 分享一个jQuery动态网格布局插件:Masonry(转)
  8. Apache Mina原理及典型例子分析
  9. background-position也许你没考虑到
  10. 利用反射把数据库查询到的数据转换成Model、List(改良版)
  11. PHP echo, print, printf, sprintf函数的区别和使用
  12. JavaScript开发中几个常用知识点总结
  13. (七)java类和对象
  14. CSS 文字太多用省略号表示
  15. Python之加密模块
  16. 【Java多线程】线程状态、线程池状态
  17. 【Mysql】【Navicat For Mac】Navicat Premium for Mac v12.0.23 + macOS Sierra 10.12.6
  18. echarts故障统计多维柱状图 堆叠柱状图 柱状图Demo2
  19. Maven入门详情
  20. UI Recorder 安装教程(一)

热门文章

  1. 常用jvm参数
  2. dedecms首页搜索 添加仿百度下拉框
  3. 859. Buddy Strings
  4. swift -inout关键字
  5. 2018.11.06 NOIP训练 最大获利(profit)(01分数规划+最大权闭合子图)
  6. python报错ModelNotFoundError
  7. 第03章:MongoDB启动参数说明
  8. if结构和逻辑运算符
  9. AIX 批量更改密码
  10. (转)忘记wamp-mysql数据库root用户密码重置方法