DotNetty项目本身的示例很容易运行起来,但是具体到真实的应用场景,还是需要进一步理解DotNetty的通道处理细节,这样才能够在实际项目应用中处理具体的问题。
  简单的场景下会有以下几个问题,第一,客户端如何向服务器主动发送消息;第二,服务器如何向指定客户端发送消息;第三,在哪里做报文的拆包和组包。我是带着以上几个问题进行分析的。

  以上几个问题,在下面的代码中会有详细的注释,也许不是标准方案,但也是应对上述问题的一种解决途径。看代码:

public partial class FrmMain : Form
{
public static object synobj = new object();
public static Int64 count = 0;
public static DateTime dt1 = DateTime.Now;
public static DateTime dt2 = DateTime.Now.AddSeconds(1);
private Timer t = new Timer();
private List<IChannel> listClients = new List<IChannel>(); public FrmMain()
{
InitializeComponent();
t.Interval = 1000;
t.Tick += T_Tick;
t.Start();
} private void T_Tick(object sender, EventArgs e)
{
this.Text = (count / (FrmMain.dt2 - FrmMain.dt1).TotalSeconds).ToString();
} /// <summary>
/// 启动服务器
/// </summary>
private async void btnStartServer_Click(object sender, EventArgs e)
{
IEventLoopGroup mainGroup;
IEventLoopGroup workerGroup; mainGroup = new MultithreadEventLoopGroup(1);
workerGroup = new MultithreadEventLoopGroup(); var bootstrap = new ServerBootstrap();
bootstrap.Group(mainGroup, workerGroup); bootstrap.Channel<TcpServerSocketChannel>(); bootstrap
.Option(ChannelOption.SoBacklog, 100)
.Handler(new LoggingHandler("SRV-LSTN"))
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
{
//每个客户端的连接创建,都会执行,channel代表了具体的连接客户端,以下过程为每个客户端连接创建编解码器。
//这里可以对channel进行统一管理,保存到列表当中,这样在主程序(服务器)中就可以针对特定的客户端(即channel)进行消息的发送。
IChannelPipeline pipeline = channel.Pipeline;
listClients.Add(channel);
pipeline.AddLast(new LoggingHandler("SRV-CONN"));
pipeline.AddLast("framing-enc", new LengthFieldPrepender(2));
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2));
//pipeline.AddLast("heart", new IdleStateHandler(0, 0, 3000 / 1000));
pipeline.AddLast("echo", new EchoServerHandler());
})); dt1 = DateTime.Now;
IChannel boundChannel = await bootstrap.BindAsync(5000); #region 模拟服务器向客户端发送消息,前提是,客户端连接后,要保存channel到列表。
//Task.Run(() =>
//{
// while (true)
// {
// for (int i = 0; i < listClients.Count; i++)
// {
// var t = listClients[i];//代表某个客户端连接
// if (t == null) { return; }
// var initialMessage = Unpooled.Buffer(256);
// byte[] messageBytes = Encoding.UTF8.GetBytes("=======发送消息给客户端=======");
// initialMessage.WriteBytes(messageBytes); // t.WriteAndFlushAsync(initialMessage);
// }
// }
//});
#endregion
} /// <summary>
/// 启动客户端
/// </summary>
private async void btnStartClient_Click(object sender, EventArgs e)
{
List<IChannel> list = new List<IChannel>();
for (int i = 0; i < 1; i++)
{
var group = new MultithreadEventLoopGroup(); var bootstrap = new Bootstrap(); bootstrap
.Group(group)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline; pipeline.AddLast(new LoggingHandler());
pipeline.AddLast("framing-enc", new LengthFieldPrepender(2));
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2));
//pipeline.AddLast("heart", new IdleStateHandler(0, 0, 3000 / 1000));
pipeline.AddLast("echo", new EchoClientHandler());
})); IChannel clientChannel = await bootstrap.ConnectAsync(textBox1.Text, 5000);
//clientChannel为客户端持有的连接对象,可以通过它主动向服务器发起请求,clientChannel.WriteAndFlushAsync()
list.Add(clientChannel);
} System.Threading.Thread.Sleep(1000); #region 模拟客户端向服务器发送消息,前提是,客户端链接后,要保存channel。
//list.ForEach(t =>
//{
// var initialMessage = Unpooled.Buffer(256);
// byte[] messageBytes = Encoding.UTF8.GetBytes("====发送消息给服务器====");
// initialMessage.WriteBytes(messageBytes); // t.WriteAndFlushAsync(initialMessage);
//});
#endregion
} private void FrmMain_Load(object sender, EventArgs e)
{
//ConsoleLoggerProvider provider = new ConsoleLoggerProvider(new ConsoleLoggerSettings());
//InternalLoggerFactory.DefaultFactory.AddProvider(provider);
}
}

  上面的代码主要是找到了服务器和客户端各自向对方发送数据的入口点,具体设计时可以对IChannel对象进行封装和维护。那么,对于我们自定义协议,我们怎样进行数据包的组包和拆包呢?答案就时上面代码中的EchoServerHandler和EchoClientHandler两个通道处理器对象。以服务器部分的代码为例:

pipeline.AddLast(new LoggingHandler("SRV-CONN"));
pipeline.AddLast("framing-enc", new LengthFieldPrepender());
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, , , , ));
//pipeline.AddLast("heart", new IdleStateHandler(0, 0, 3000 / 1000));
pipeline.AddLast("echo", new EchoServerHandler());

  服务中创建了以上四个IChannel接口对象,他们之间是什么关系呢?顺序执行!接收和发送都按照AddLast的先后顺序执行。如接收数据时,先做日志处理,再做解码LengthFieldBasedFrameDecoder,最后做EchoServerHandler的自定义处理,因为是接收,所以不做编码LengthFieldPrepender这个处理,这是DotNetty内部判断的,LengthFieldPrepender是继承了MessageToMessageEncoder的,MessageToMessageEncoder本身就代表了编码操作,而接收数据不需要做编码,所以这个操作会被略过。LengthFieldPrepender是在服务器发送数据时才做。

  每个处理过程都接收上个处理过程的处理结果,比如EchoServerHandler接收到的数据,是LengthFieldBasedFrameDecoder处理完成后的输出。演示程序的协议类型是头部两个字节代表数据包长度,后面是数据体,这样在LengthFieldBasedFrameDecoder处理完成后,EchoServerHandler接收到的是不包含描述长度的两个字节,只有数据体部分的数据,这样我们就可以在自定义的EchoServerHandler中,进行数据体的拆包操作了。

  EchoServerHandler和EchoClientHandler的代码如下:

public class EchoServerHandler : ChannelHandlerAdapter
{
public override void ChannelRead(IChannelHandlerContext context, object message)
{
var buffer = message as IByteBuffer;
if (buffer != null)
{
lock (FrmMain.synobj)
{
FrmMain.count++;
}
FrmMain.dt2 = DateTime.Now;
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId + "Received from client: " + buffer.ToString(Encoding.UTF8) + "=" + FrmMain.count / (FrmMain.dt2 - FrmMain.dt1).TotalSeconds);
}
context.WriteAsync(message);
} public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush(); public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
Console.WriteLine("Exception: " + exception);
context.CloseAsync();
}
}

  

public class EchoClientHandler : ChannelHandlerAdapter
{
readonly IByteBuffer initialMessage; public EchoClientHandler()
{
this.initialMessage = Unpooled.Buffer(256);
byte[] messageBytes = Encoding.UTF8.GetBytes("Hello world");
this.initialMessage.WriteBytes(messageBytes);
} public override void ChannelActive(IChannelHandlerContext context) => context.WriteAndFlushAsync(this.initialMessage); public override void ChannelRead(IChannelHandlerContext context, object message)
{
var byteBuffer = message as IByteBuffer;
if (byteBuffer != null)
{
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId + "Received from server: " + byteBuffer.ToString(Encoding.UTF8));
} //System.Threading.Thread.Sleep(500);
context.WriteAsync(message);
} public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush(); public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
Console.WriteLine("Exception: " + exception);
context.CloseAsync();
}
}

  

DotNetty学习笔记的更多相关文章

  1. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  2. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  3. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  4. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  5. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  6. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  7. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

  8. HTML学习笔记

    HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...

  9. DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记

    今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...

随机推荐

  1. 并发集合 System.Collections.Concurrent 命名空间

    System.Collections.Concurrent 命名空间提供多个线程安全集合类. 当有多个线程并发访问集合时,应使用这些类代替 System.Collections 和 System.Co ...

  2. Alpha Scrum3

    Alpha Scrum3 牛肉面不要牛肉不要面 Alpha项目冲刺(团队作业5) 各个成员在 Alpha 阶段认领的任务 林志松:音乐网页前端页面编写,博客发布 林书浩.陈远军:界面设计.美化 吴沂章 ...

  3. Linux CPU的中断【转载】

    中断其实就是由硬件或软件所发送的一种称为IRQ(中断请求)的信号. 中断允许让设备,如键盘,串口卡,并口等设备表明它们需要CPU. 一旦CPU接收了中断请求,CPU就会暂时停止执行正在运行的程序,并且 ...

  4. Mac下安装sbt

    为了可以用Eclipse编译基于Scala的Spark Project,需要安装sbt 环境:OS X Yosemite 10.10.3 1.安装Xcode 因为要在终端用macports安装sbt, ...

  5. redis.conf 具体配置详解

    redis.conf 具体配置详解 # redis 配置文件示例 # 当你需要为某个配置项指定内存大小的时候,必须要带上单位, # 通常的格式就是 1k 5gb 4m 等酱紫: # # 1k => ...

  6. [转载] MySQL 注入攻击与防御

    MySQL 注入攻击与防御 2017-04-21 16:19:3454921次阅读0     作者:rootclay 预估稿费:500RMB 投稿方式:发送邮件至linwei#360.cn,或登陆网页 ...

  7. 非接触式读卡器13.56MHZ芯片:SI522

    对于现在的智能锁市场需求竞争极大,中小型厂家月销量更是在慢慢增长.刷卡功能更是智能锁的标配功能,当然13.56Mhz芯片现在讲究的就是超低功耗,为满足市场需求专注于物联网多年的中科微强力推出了13.5 ...

  8. Java中的集合框架-Map

    前两篇<Java中的集合框架-Commection(一)>和<Java中的集合框架-Commection(二)>把集合框架中的Collection开发常用知识点作了一下记录,从 ...

  9. selenium java maven 自动化测试(一) helloworld

    本教程使用selenium-java,简单的完成了网页访问 网页内容获取,表单填写以及按钮点击. 1. 使用maven构建项目 在pom中添加如下依赖: <dependency> < ...

  10. oracle中查看一张表是否有主键,主键在哪个字段上

    利用Oracle中系统自带的两个视图可以实现查看表中主键信息,语句如下:select a.constraint_name, a.column_name from user_cons_columns a ...