欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用 DotNetty 实现 Redis 的一个控制台应用程序

程序员文章站 2022-05-25 16:24:08
零:Demo 跑出来的结果如图 上图说明 图中左边蓝色的命令行界面,是用windows powershell 命令行链接的。 1.打开powershell命令行界面,输入命令【telnet 127.0.0.1 6379】。 如果没有powershell,使用cmd 命令行界面也是可以达到测试redi ......

零:demo 跑出来的结果如图

使用 DotNetty 实现 Redis 的一个控制台应用程序

上图说明

图中左边蓝色的命令行界面,是用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

 

使用 DotNetty 实现 Redis 的一个控制台应用程序
 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         /// 以 : 开头的整型数据integer
14         /// </summary>
15         integer = 58,
16         /// <summary>
17         /// 以 $ 开头的多行字符串
18         /// </summary>
19         bulkstring = 36,
20 
21         /// <summary>
22         /// 以 * 开头的数组
23         /// </summary>
24         arrayheader = 42
25     }
view code

 

2,定义redisobject   并定义了虚拟的方法 writebuffer

使用 DotNetty 实现 Redis 的一个控制台应用程序
 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\n
18             //const string headstr = "*{0}\r\n";
19             //参数信息       $<number of bytes of argument n>\r\n<argument data>\r\n
20             //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\n
28             byte[] bytes = encoding.utf8.getbytes(stringbuilder.tostring());
29             output.writebytes(bytes);
30         }
31     }
view code

 

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

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