使用 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 种最小单元类型,单元结束时统一加上回车换行符号\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 的一个控制台应用程序的更多相关文章
- 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度。 2)输出字符串中第一个出现字母a的位置。 3)在字符串的第3个字符后面插入子串“hello”,输出新字符串。 4)将字符串“hello”替换为“me”,输出新字符串。 5)以字符“m”为分隔符,将字符串分离,并输出分离后的字符串。 */
namespace test4 {/* 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度. 2)输出字符串中第一个出现字母a的位置. 3)在字符串的第3个字符 ...
- C#编写一个控制台应用程序,输入正方形边长或者半径,计算其周长和面积并输出
编写一个控制台应用程序,输入正方形边长或者半径,计算其周长和面积并输出 (1) 编写两个接口,接口 IShape 包含三个方法:initialize, getPerimeter, getArea.分别 ...
- C#设计编写一个控制台应用程序
设计编写一个控制台应用程序,练习类的继承. (1) 编写一个抽象类 People,具有"姓名","年龄"字段,"姓名"属性,Work 方法. ...
- C#编写一个控制台应用程序,可根据输入的月份判断所在季节
编写一个控制台应用程序,可根据输入的月份判断所在季节 代码: using System; using System.Collections.Generic; using System.Linq; us ...
- C#编写一个控制台应用程序,输入三角形或者长方形边长,计算其周长和面积并输出
编写一个控制台应用程序,输入三角形或者长方形边长,计算其周长和面积并输出. 代码: using System; using System.Collections.Generic; using Syst ...
- 制作一个控制台小程序,要求:用户可以在控制到录入学生的姓名,当用户输入quit(不区分大小写)时,程序停止接收用户输入,并且显示出学生个数及姓名
string name = string.Empty; //定义一个集合来接收学生 List<string> my = new List<string>(); do { Con ...
- asp.net mvc引用控制台应用程序exe
起因:有一个控制台应用程序和一个web程序,web程序想使用exe程序的方法,这个时候就需要引用exe程序. 报错:使用web程序,引用exe程序 ,vs调试没有问题,但是部署到iis就报错,如下: ...
- Web应用程序或者WinForm程序 调用 控制台应用程序及参数传递
有时候在项目中,会调用一个控制台应用程序去处理一些工作.那在我们的程序中要怎么样做才能调用一个控制台应用程序并将参数传递过去,控制台程序执行完后,我们的程序又怎样获取返回值?代码如下:调用代码: ...
- 完全不借助VS,编写C#控制台应用程序
(因为这个必须要借助控制台,所以必须是控制台应用程序) csc.exe是微软.NET Framework 中的C#编译器 步骤如下: 1)用记事本写一个控制台应用程序的代码,保存在E盘,test.cs ...
随机推荐
- centos6.5虚拟机每次都要ifup eth0的解决办法
修改文件/etc/sysconfig/network-scripts/ifcfg-eth0把ONBOOT=no改ONBOOT=yes
- 在 Anaconda下解决国内安装tensorflow等下载慢和中断,出错,异常问题的一点思路
把镜像地址改为清华大学开源软件镜像站,打开 管理员身份打开cmd 输入conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/ ...
- hadoop web管理界面不能打开问题
centos 7 安装好hadoop的,hadoop和yarn都正常启动,但是yarn的web界面(8088),hdfs的web界面(50070)都不能打开,防火墙是处于关闭状态. 修改默认启动级别, ...
- 2019.01.13 bzoj1146: [CTSC2008]网络管理Network(整体二分+树剖)
传送门 题意简述:给一棵树,支持单点修改,询问路径上两点间第kkk大值. 思路: 读懂题之后立马可以想到序列上带修区间kkk大数的整体二分做法,就是用一个bitbitbit来支持查值. 那么这个题把树 ...
- Lyft Level 5 Challenge 2018 - Final Round (Open Div. 2) C. The Tower is Going Home(思维+双指针)
https://codeforces.com/contest/1075/problem/C 题意 一个宽为1e9*1e9的矩阵中的左下角,放置一个车(车可以移动到同一行或同一列),放置一些墙,竖的占据 ...
- n维向量空间W中有子空间U,V,如果dim(U)=r dim(V)=n-r U交V !={0},那么U,V的任意2组基向量的组合必定线性相关
如题取U交V中的向量p (p!=0), 那么p可以由 U中的某一组基线性组合成(系数不全是零),同时,-p也可以由V中的某一组基线性组合成(系数不全为零) 考察p+(-p)=0 可知道,U中的这组基跟 ...
- new命令简化的内部流程
构造函数返回对象的一些问题: function fn(name,age){ this.name = name; this.age = age; //return 23; 忽略数字,直接返回原有对象 / ...
- MongoDB-增删改
MongoDB的shell使用了Js引擎,因此能运行任意的Js程序. MongoDB中常用基本数据类型: null:空值或者不存在的字段Boolean:true,false数值型:{"x&q ...
- 添加wifi adb
https://blog.csdn.net/ouyang_peng/article/details/50370786 首先弄懂怎么设置adb wifi无线调试的功能,如下所示. 1. 手机端开启adb ...
- 微信小程序之画布
canvas 标签默认宽度300px.高度225px 同一页面中的 canvas-id 不可重复,如果使用一个已经出现过的 canvas-id,该 canvas 标签对应的画布将被隐藏并不再正常工作 ...
