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

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

使用 DotNetty 实现 Redis 的一个控制台应用程序的更多相关文章

  1. 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度。 2)输出字符串中第一个出现字母a的位置。 3)在字符串的第3个字符后面插入子串“hello”,输出新字符串。 4)将字符串“hello”替换为“me”,输出新字符串。 5)以字符“m”为分隔符,将字符串分离,并输出分离后的字符串。 */

    namespace test4 {/* 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度. 2)输出字符串中第一个出现字母a的位置. 3)在字符串的第3个字符 ...

  2. C#编写一个控制台应用程序,输入正方形边长或者半径,计算其周长和面积并输出

    编写一个控制台应用程序,输入正方形边长或者半径,计算其周长和面积并输出 (1) 编写两个接口,接口 IShape 包含三个方法:initialize, getPerimeter, getArea.分别 ...

  3. C#设计编写一个控制台应用程序

    设计编写一个控制台应用程序,练习类的继承. (1) 编写一个抽象类 People,具有"姓名","年龄"字段,"姓名"属性,Work 方法. ...

  4. C#编写一个控制台应用程序,可根据输入的月份判断所在季节

    编写一个控制台应用程序,可根据输入的月份判断所在季节 代码: using System; using System.Collections.Generic; using System.Linq; us ...

  5. C#编写一个控制台应用程序,输入三角形或者长方形边长,计算其周长和面积并输出

    编写一个控制台应用程序,输入三角形或者长方形边长,计算其周长和面积并输出. 代码: using System; using System.Collections.Generic; using Syst ...

  6. 制作一个控制台小程序,要求:用户可以在控制到录入学生的姓名,当用户输入quit(不区分大小写)时,程序停止接收用户输入,并且显示出学生个数及姓名

    string name = string.Empty; //定义一个集合来接收学生 List<string> my = new List<string>(); do { Con ...

  7. asp.net mvc引用控制台应用程序exe

    起因:有一个控制台应用程序和一个web程序,web程序想使用exe程序的方法,这个时候就需要引用exe程序. 报错:使用web程序,引用exe程序 ,vs调试没有问题,但是部署到iis就报错,如下: ...

  8. Web应用程序或者WinForm程序 调用 控制台应用程序及参数传递

    有时候在项目中,会调用一个控制台应用程序去处理一些工作.那在我们的程序中要怎么样做才能调用一个控制台应用程序并将参数传递过去,控制台程序执行完后,我们的程序又怎样获取返回值?代码如下:调用代码:    ...

  9. 完全不借助VS,编写C#控制台应用程序

    (因为这个必须要借助控制台,所以必须是控制台应用程序) csc.exe是微软.NET Framework 中的C#编译器 步骤如下: 1)用记事本写一个控制台应用程序的代码,保存在E盘,test.cs ...

随机推荐

  1. Java第15章笔记

    字符串的概述 1.什么是字符串:零个或多个字符组成的有限序列 2.如何使用字符串:(使用字符串分为两步)          1)定义并初始化字符串          2)使用字符,对字符串进行一些处理 ...

  2. MyBatis 实现新增

    MyBatis实现新增 1.概念学习:(角度不同) 1.1 功能:从应用程序角度出发,软件具有哪些功能 1.2 业务:完成功能时的逻辑,对应Service中一个方法 1.3 事务:从数据库角度出发,完 ...

  3. html--笔记day03

    html--笔记day03 1.结构标记 1.<header>元素 <header></header> ==> <div id="header ...

  4. hadoop mapreduce 写入hbase报错 Session 0x0 for server null, unexpected error, closing socket connection and attempting reconnect

    现象:map任务构造数据正常,reduce任务,开始也正常,速度很快 ,在hbase 的管理界面,可以看到,5W以上的请求数 当reduce 执行到 70% 左右的时候,就堵住了,查看yarn的web ...

  5. 如何将frm文件导入MySql数据库

    只要在mysql的安装文件中找到data文件夹,然后在里面建立一个文件夹,比如test.这个test其实就对应着数据库的名称,所以,你想要起什么样的数据库名称就把文件夹起什么名字. 然后把.frm文件 ...

  6. 2018.10.29 NOIP训练 数据结构(带修改莫队)

    传送门 带修莫队板题. 直接按照经典写法做就行了. 代码

  7. javascript捕获页面窗口关闭事件

    javascript捕获窗口关闭事件有两种方法 onbeforeunload()      ,onUnload() 用法有两种: 1.    function      window.onbefore ...

  8. 天使投资、A轮、B轮、C轮

    一般是这样划分的. A轮融资:公司产品有了成熟模样,开始正常运作一段时间并有完整详细的商业及盈利模式,在行业内拥有一定地位和口碑.公司可能依旧处于亏损状态.资金来源一般是专业的风险投资机构(VC).投 ...

  9. max10之pll时钟源切换

    问题3:PLL切换功能中,多次切换可能造成PLL锁不定 从现象看clkbadx信号是不影响的,但locked信号一定是有影响的.

  10. css3动画特效集合

    <!DOCTYPE html> <html> <head> <title></title> <meta charset="u ...