zoukankan      html  css  js  c++  java
  • 使用 DotNetty 实现 Redis 的一个控制台应用程序

    零: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 种最小单元类型,单元结束时统一加上回车换行符号 ,来表示该单元的结束。

      单行字符串 以 + 符号开头。

      多行字符串 以 $ 符号开头,后跟字符串长度。

      整数值 以 : 符号开头,后跟整数的字符串形式。

      错误消息 以 - 符号开头。

      数组 以 * 号开头,后跟数组的长度。

      关于 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         /// 以 : 开头的整型数据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

     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>
    
    18             //const string headstr = "*{0}
    ";
    19             //参数信息       $<number of bytes of argument N>
    <argument data>
    
    20             //const string bulkstr = "${0}
    {1}
    ";
    21             StringBuilder stringBuilder = new StringBuilder();
    22             stringBuilder.AppendFormat("*{0}
    ",Commond.Count);
    23             foreach (var item in Commond)
    24             {
    25                 stringBuilder.AppendFormat("${0}
    {1}
    ",item.Length,item);
    26             }
    27             //*1
    $4
    PING
    
    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 服务端要返回一个多行字符串,它的返回格式为:  ${字符串长度} {字符串}

        解析多行字符串的代码为

      

            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

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

  • 相关阅读:
    Send or receive files via Xshell
    git archive命令详解
    test命令详解
    shell中的数学运算
    深入理解文件权限
    rebuild online时意外中断 再次重建时报错解决方法
    关于临时表空间,在日常生产中会遇到的问题
    Oracle对于敏感数据的处理,可以采用策略(dbms_rls.add_policy)
    我对于B-树索引的内部结构与索引类型所做的笔记
    记一次ADG备库归档目录满导致的延时处理
  • 原文地址:https://www.cnblogs.com/ruxia/p/9477389.html
Copyright © 2011-2022 走看看