第一,客户端如何向服务器主动发送消息;

第二,服务器如何向指定客户端发送消息;

第三,在哪里做报文的拆包和组包。

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(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());

服务中创建了以上四个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. DotNetty学习笔记

    DotNetty项目本身的示例很容易运行起来,但是具体到真实的应用场景,还是需要进一步理解DotNetty的通道处理细节,这样才能够在实际项目应用中处理具体的问题. 简单的场景下会有以下几个问题,第一 ...

  2. Dubbo Mesh 在闲鱼生产环境中的落地实践

    本文作者至简曾在 2018 QCon 上海站以<Service Mesh 的本质.价值和应用探索>为题做了一次分享,其中谈到了 Dubbo Mesh 的整体发展思路是“借力开源.反哺开源” ...

  3. PS小技巧之完美抠图

    具体详细步骤如下01.打开图片,ctrl+j复制一层得到图层1,点击红圈处新建图层2,放于图层1与背景层之间,填充你喜欢的颜色,作为检查效果和新的背景图层. 02.点击图层1,用“快速选择工具”大致做 ...

  4. Channel概述

    前言 前两篇文章介绍了NIO核心部分部分之一的缓冲区的相关内容,接下来我们继续学习NIO中另一个重要的核心部分--Channel(通道). 在学习这篇文章之前,先做下简单的说明,本文是一篇关于通道的概 ...

  5. 使用DotNetty编写跨平台网络通信程序

    长久以来,.Net开发人员都非常羡慕Java有Netty这样,高效,稳定又易用的网络通信基础框架.终于微软的Azure团队,使用C#实现的Netty的版本发布.不但使用了C#和.Net平台的技术特点, ...

  6. 通信传输利器Netty(Net is DotNetty)介绍

    (先埋怨一下微软大大)我们做NET开发,十分羡慕JAVA上能有NETTY, SPRING, STRUTS, DUBBO等等优秀框架,而我们NET就只有干瞪眼,哎,无赖之前生态圈没做好,恨铁不成钢啊.不 ...

  7. .NET Core微服务之路:利用DotNetty实现一个简单的通信过程

    上一篇我们已经全面的介绍过<基于gRPC服务发现与服务治理的方案>,我们先复习一下RPC的调用过程(笔者会在这一节的几篇文章中反复的强调这个过程调用方案),看下图

  8. DotNetty 跨平台的网络通信库

    长久以来,.Net开发人员都非常羡慕Java有Netty这样,高效,稳定又易用的网络通信基础框架.终于微软的Azure团队,使用C#实现的Netty的版本发布.不但使用了C#和.Net平台的技术特点, ...

  9. DotNetty 跨平台的网络通信库(转)

    久以来,.Net开发人员都非常羡慕Java有Netty这样,高效,稳定又易用的网络通信基础框架.终于微软的Azure团队,使用C#实现的Netty的版本发布.不但使用了C#和.Net平台的技术特点,并 ...

随机推荐

  1. jexus配置支持Owin

    vi打开配置文件,加一行 OwinMain=xxx.dll ###################### # Web Site: Default ########################### ...

  2. 如何在Axure中使用FontAwesome字体图标

    Font Awesome为您提供可缩放的矢量图标,您可以使用CSS所提供的所有特性对它们进行更改,包括:大小.颜色.阴影或者其它任何支持的效果. FontAwesome应用在web网页开发中非常方便, ...

  3. LeetCode(20):有效的括号

    Easy! 题目描述: 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足: 左括号必须用相同类型的右括号闭合. 左括号必须以正确的顺序闭 ...

  4. Intellij IDEA Debug调试技巧

    1.这里以一个web工程为例,点击图中按钮开始运行web工程. 2.设置断点 3.使用postman发送http请求 4.请求发送之后会自动跳到断点处,并且在断点之前会有数据结果显示 5.按F8 在 ...

  5. 解决Eclipse启动报错【Failed to create the Java Virtual Machine】

    电脑:2G内存,WIN7 32位. 启动adt-bundle-windows-x86-20140702\eclipse\eclipse.exe时,报错[Failed to create the Jav ...

  6. Codeforces 908F New Year and Rainbow Roads

    New Year and Rainbow Roads 思路:我们考虑两个绿点之间的红点和蓝点, 首先把这些红点和蓝点接到绿点上面绝对不会超过绿点距离的两倍. 然后我们先把两个绿点连上, 再把绿点经过蓝 ...

  7. Linux 获取目录中最后一个文件的名字

    find /application/docker_hub/logs/fof1private/amount_dev -type l | xargs basename

  8. chown 详解

    chown Linux/Unix 是多用户多任务操作系统,所有的文件皆有其拥有者(Owner).利用 chown命令 可以将文件的拥有者加以改变.一般来说,这个命令只能由系统管理者(root)使用,一 ...

  9. 深刻领悟javascript中的exec与match方法之异同

    阅读本文之前,请先看下面一道题: 题目17:Read the following javascript code:  var someText="web2.0 .net2.0"; ...

  10. hashCode()方法与equals()方法

    摘自别人的评论:http://blog.csdn.net/fhm727/article/details/5221792 当向集合Set中增加对象时,首先集合计算要增加对象的hashCode码,根据该值 ...