DotNetty 跨平台的网络通信库

久以来,.Net开发人员都非常羡慕Java有Netty这样,高效,稳定又易用的网络通信基础框架。终于微软的Azure团队,使用C#实现的Netty的版本发布。不但使用了C#和.Net平台的技术特点,并且保留了Netty原来绝大部分的编程接口。让我们在使用时,完全可以依照Netty官方的教程来学习和使用DotNetty应用程序。
DotNetty同时也是开源的,它的源代码托管在Github上:https://github.com/azure/dotnetty

0x01 项目预览

从github上下载最新的代码到本地,使用VS2017或者VSCode打开下载好的代码,可以看到如图所示的代码那结构,其中源码部分有9个项目组成,其中

DotNetty.Common 是公共的类库项目,包装线程池,并行任务和常用帮助类的封装
DotNetty.Transport 是DotNetty核心的实现
DotNetty.Buffers 是对内存缓冲区管理的封装
DotNetty.Codes 是对编解码是封装,包括一些基础基类的实现,我们在项目中自定义的协议,都要继承该项目的特定基类和实现
DotNetty.Handlers 封装了常用的管道处理器,比如Tls编解码,超时机制,心跳检查,日志等,如果项目中没有用到可以不引用,不过一般都会用到
其他还有对Redis的编解码,Mqtt的编解码,Protobuf2/3的编解码项目中可根据实际情况引用
很遗憾Http协议和Websocket协议还没有实现。

0x02 快速开始-示例-回声程序的实现

从上一步下载的代码中,看到有一个sample目录,有很多例子,都大同小异, 先来看这个最简单的Echo服务的实现吧.
Echo服务,分为服务端和客户端,服务端使用DotNetty框架启动一个Socket服务,并等待客户端链接,当客户端链接并接收客户端消息,并将接收到的消息原样返回给客户端。而客户端同样使用DotNetty框架启动一个Socket客户端服务,并链接到服务端,并发送一条Hello的字符串信息,并等待服务端返回。如此往复。

2.1 Echo Server

来一起看一下代码吧,我把注释都写到代码中:

static async Task RunServerAsync() {    //设置输出日志到Console    ExampleHelper.SetConsoleLogger();    // 主工作线程组,设置为1个线程    var bossGroup = new MultithreadEventLoopGroup(1);    // 工作线程组,默认为内核数*2的线程数    var workerGroup = new MultithreadEventLoopGroup();    X509Certificate2 tlsCertificate = null;   
     if (ServerSettings.IsSsl) //如果使用加密通道            {                tlsCertificate = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");            }          
                try            {                                //声明一个服务端Bootstrap,每个Netty服务端程序,都由ServerBootstrap控制,                //通过链式的方式组装需要的参数                var bootstrap = new ServerBootstrap();                bootstrap                    .Group(bossGroup, workerGroup) // 设置主和工作线程组                    .Channel<TcpServerSocketChannel>() // 设置通道模式为TcpSocket                    .Option(ChannelOption.SoBacklog, 100) // 设置网络IO参数等,这里可以设置很多参数,当然你对网络调优和参数设置非常了解的话,你可以设置,或者就用默认参数吧                    .Handler(new LoggingHandler("SRV-LSTN")) //在主线程组上设置一个打印日志的处理器                    .ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel =>                    { //工作线程连接器 是设置了一个管道,服务端主线程所有接收到的信息都会通过这个管道一层层往下传输//同时所有出栈的消息 也要这个管道的所有处理器进行一步步处理                        IChannelPipeline pipeline = channel.Pipeline;                        if (tlsCertificate != null) //Tls的加解密                        {                            pipeline.AddLast("tls", TlsHandler.Server(tlsCertificate));                        }                        //日志拦截器                        pipeline.AddLast(new LoggingHandler("SRV-CONN"));//出栈消息,通过这个handler 在消息顶部加上消息的长度                        pipeline.AddLast("framing-enc", new LengthFieldPrepender(2));//入栈消息通过该Handler,解析消息的包长信息,并将正确的消息体发送给下一个处理Handler,该类比较常用,后面单独说明                        pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2));//业务handler ,这里是实际处理Echo业务的Handler                        pipeline.AddLast("echo", new EchoServerHandler());                    }));                    // bootstrap绑定到指定端口的行为 就是服务端启动服务,同样的Serverbootstrap可以bind到多个端口                IChannel boundChannel = await bootstrap.BindAsync(ServerSettings.Port);                Console.ReadLine();//关闭服务                await boundChannel.CloseAsync();            }          
               finally            {//释放工作组线程              
 await Task.WhenAll(                    bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),                    workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));            }        }

来看下实际的业务代码,比较简单,也就是打印日志,并返回收到的字符串

 public class EchoServerHandler : ChannelHandlerAdapter //管道处理基类,较常用    {//  重写基类的方法,当消息到达时触发,这里收到消息后,在控制台输出收到的内容,并原样返回了客户端        public override void ChannelRead(IChannelHandlerContext context, object message)        {            var buffer = message as IByteBuffer;            if (buffer != null)            {                Console.WriteLine("Received from client: " + buffer.ToString(Encoding.UTF8));            }            context.WriteAsync(message);//写入输出流        }// 输出到客户端,也可以在上面的方法中直接调用WriteAndFlushAsync方法直接输出        public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();//捕获 异常,并输出到控制台后断开链接,提示:客户端意外断开链接,也会触发        public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)        {            Console.WriteLine("Exception: " + exception);            context.CloseAsync();        }    }

2.2 Echo Client

客户端的代码和服务端的代码相差很少,体现了Netty统一的编程模型。有几个不同点:

  1. 客户端的Bootstrap不是ServerBootstrap了,

  2. 客户端不需要主线程组,只有工作线程组,消息处理管道也建立在里主线程工作组的拦截通道上。

  3. 最后不是bind而是connect

static async Task RunClientAsync()        {            ExampleHelper.SetConsoleLogger();       
              var group = new MultithreadEventLoopGroup();            X509Certificate2 cert = null;            string targetHost = null;            if (ClientSettings.IsSsl)            {                cert = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");                targetHost = cert.GetNameInfo(X509NameType.DnsName, false);            }            
                 try            {              
                  var bootstrap = new Bootstrap();                bootstrap                    .Group(group)                    .Channel<TcpSocketChannel>()                    .Option(ChannelOption.TcpNodelay, true)                    .Handler(new ActionChannelInitializer<ISocketChannel>(channel =>                    {                        IChannelPipeline pipeline = channel.Pipeline;                        if (cert != null)                        {                            pipeline.AddLast("tls", new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(targetHost)));                        }                        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("echo", new EchoClientHandler());                    }));                IChannel clientChannel = await bootstrap.ConnectAsync(new IPEndPoint(ClientSettings.Host, ClientSettings.Port));                Console.ReadLine();                await clientChannel.CloseAsync();            }            finally            {                await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));            }        }

业务代码

// 代码和服务端也相差不多,并且继承了同样的基类。 public class EchoClientHandler : ChannelHandlerAdapter    {      
    readonly IByteBuffer initialMessage;      
 public EchoClientHandler()
       {            this.initialMessage = Unpooled.Buffer(ClientSettings.Size);          
 byte[] messageBytes = Encoding.UTF8.GetBytes("Hello world");            this.initialMessage.WriteBytes(messageBytes);        }    //重写基类方法,当链接上服务器后,马上发送Hello World消息到服务端        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("Received from server: " + byteBuffer.ToString(Encoding.UTF8));            }            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();        }    }

0x03 常用Handler和基类

从Echo服务的例子中,我们可以看到Netty程序不管时服务端还是客户端都通过一个Bootstrap/ServerBootstrap来启动Socket程序,并通过设置处理Handler管道来处理出入的消息,管道中常见的拦截器有加解密,日志记录,编解码,消息头处理,业务处理等,实际业务中根据情况可以自行添加自己的业务逻辑,同时很多处理器代码在服务端和客户端是公用的,Netty本身已经提供了一些常用处理器和业务处理器的基类来简化实际开发,我们一起看一下

3.1 TlsHandler

Netty支持Tls加密传输,TlsHandler类可以在开发人员无须关心加密传输时字节码的变化,只关心自己的业务代码即可。在管道处理的第一个配置该类即可

3.2 LengthFieldPrepender

这个handler 会在实际发送前在将数据的长度放置在数据前,本例中使用2个字节来存储数据的长度。

3.3 LengthFieldBasedFrameDecoder

这个handler比较常用,会在解码前用于解析数据,用于读取数据包的头信息,特别是包长,并等待数据达到包长后再交由下一个handler处理。
参数说明 以下是Amp协议的参数值,并注释了意义

InitialBytesToStrip = 0, //读取时需要跳过的字节数
LengthAdjustment = -5, //包实际长度的纠正,如果包长包括包头和包体,则要减去Length之前的部分
LengthFieldLength = 4, //长度字段的字节数 整型为4个字节
LengthFieldOffset = 1, //长度属性的起始(偏移)位
MaxFrameLength = int.MaxValue, // 最大包长

3.4 ChannelHandlerAdapter和SimpleChannelInboundHandler

业务处理的常用Handler基类,一般客户端和服务端的业务处理handler 都要继承这个这两个类,其中SimpleChannelInboundHandler是ChannelHandlerAdapter的子类,对其简单的进行封装,并进行了类型检查。

3.5 IdleStateHandler 链接状态检查handler

这个handler一般用于检查链接的状态,比如写超时,读超时。在实际项目中一般在客户端添加它,并用于发送心跳包。

以下是DotBPE在客户端管道中 第一个添加IdleStateHandler 并设置触发时间

 var bootstrap = new Bootstrap();bootstrap.Channel<TcpSocketChannel>().Option(ChannelOption.TcpNodelay, true).Option(ChannelOption.ConnectTimeout, TimeSpan.FromSeconds(3)).Group(new MultithreadEventLoopGroup())                .Handler(new ActionChannelInitializer<ISocketChannel>(c =>                {             
       var pipeline = c.Pipeline;                    pipeline.AddLast(new LoggingHandler("CLT-CONN"));                    MessageMeta meta = _msgCodecs.GetMessageMeta();                    // IdleStateHandler                    pipeline.AddLast("timeout", new IdleStateHandler(0, 0, meta.HeartbeatInterval / 1000));                    //消息前处理                    pipeline.AddLast(                        new LengthFieldBasedFrameDecoder(                            meta.MaxFrameLength,                            meta.LengthFieldOffset,                            meta.LengthFieldLength,                            meta.LengthAdjustment,                            meta.InitialBytesToStrip                        )                    );                    pipeline.AddLast(new ChannelDecodeHandler<TMessage>(_msgCodecs));                    pipeline.AddLast(new ClientChannelHandlerAdapter<TMessage>(this));                }));            return bootstrap;

然后在业务处理handler中处理UserEventTriggered事件

//ChannelHandlerAdapter 重写UserEventTriggeredpublic override void UserEventTriggered(IChannelHandlerContext context, object evt){  if(evt is IdleStateEvent){     var eventState = evt as IdleStateEvent;     if(eventState !=null){        this._bootstrap.SendHeartbeatAsync(context,eventState);     }  } }

更多细节可以参考 《Netty 4.x 用户指南》

相关文章: 

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

  • Unity/DotNetty中集成Lidgren实现可靠UDP

  • SuperSocket与Netty之实现protobuf协议,包括服务端和客户端

原文地址:http://www.cnblogs.com/MuNet/p/8546309.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

26、临时表的创建和重复数据的处理

UPDATE student b SET b.sname dd WHERE b.id (SELECT a.id FROM student a WHERE a.id 3) Mysql中根据条件&#xff08;表A中的字段&#xff09;操作表A中的数据时是不可以的 所以借助临时表来删除/更新重复的数据&#xff0c;原理就是删除每组重复数据中除id值最大的其他…

1、java简介

关于java介绍也没什么好说的&#xff0c;在这里简单介绍一下&#xff0c;说起java&#xff0c;我第一想到的就是它的简单和强大&#xff0c;简单是简单易学&#xff0c;开发速度快&#xff1b;强大是其功能强大&#xff0c;各个领域都可使用&#xff0c;其代码一次编译可以处处…

C# 观察者模式 以及 delegate 和 event

观察者模式这里面综合了几本书的资料.需求有这么个项目: 需求是这样的:一个气象站, 有三个传感器(温度, 湿度, 气压), 有一个WeatherData对象, 它能从气象站获得这三个数据. 还有三种设备, 可以按要求展示气象站的最新数据.WeatherData的结构如下:有3个get方法, 分别获取最新的…

2、JAVA开发环境的搭建

上次说到java应用之所以做到跨平台&#xff0c;是因为其依赖于java虚拟机&#xff0c;java想要运行需要依赖于特定的运行环境&#xff0c;称为JRE&#xff0c;如果想要开发java应用&#xff0c;则需要用到开发工具包&#xff0c;也就是JDK&#xff0c;所以这里就要说一下这几者…

Serilog Tutorial

在过去的几年中&#xff0c;结构化日志已经大受欢迎。而Serilog是 .NET 中最著名的结构化日志类库 ,我们提供了这份的精简指南来帮助你快速了解并运用它。0. 内容设定目标认识Serilog事件和级别触发和收集结构化数据为过滤和关联添加事件标记大海捞针 [Finding needles in the …

使用DDD、事件风暴和Actor来设计反应式系统

领域驱动设计&#xff08;domain-driven design&#xff0c;DDD&#xff09;通常在微服务领域用于查找边界&#xff08;限界上下文&#xff09;。同样来自DDD的聚合&#xff08;aggregate&#xff09;对于定义持久化和一致性的范围来讲也是很重要的。 但是&#xff0c;并不是领…

站在巨人肩上的.NET Core 2.1

.NET Core 1.0自发布两年以来&#xff0c;得到了开发者群体相当高地认可。 下图来自Stack overflow survey 2018的统计&#xff1a;.NET Core已经成为前五的主流框架工具&#xff0c;现今借鉴了优秀的设计原则和开发体验可谓站在巨人肩上。这一切归功于.NET团队认识和总结了大量…

5、java中的数组

1、简介 数组是一种具有随机存取特性的数据结构&#xff0c;是内存上一段连续区域的表示&#xff0c;是实现顺序存储的基础&#xff0c;数组只能用于存储同一类型的数据。数组的长度在初始化时定义之后就不可更改&#xff0c;并且在初始化数组时必须指定数组的长度。 2、数组…

动态规划训练19、最短路 [Help Jimmy POJ - 1661 ]

Help Jimmy POJ - 1661 题意&#xff1a;大致是一个人从某个点开始下落&#xff0c;下落的速度是1m/s&#xff0c;然后在平台上的时候可以左右移动&#xff0c;移动的速度也是1m/s&#xff0c;但是这里有一个限制&#xff0c;就是说每次下落的距离不能超过一个给定的数值。问你…

【活动(北京)】Global Azure Bootcamp

活动议程活动内容08:30-08:50报到08:50-09:10活動开场Study4 - 陈科融(MVP)STB Chain Foundation - 劉海峰(MVP)MVP Program - Christina Liang(MVP CPM)09:10-10:00区块链让软件资产化成为现实刘海峰(MVP) - STBChain Foundation主席10:10-11:00基于Azure PaaS的网站应用刘元纶…

6、java中的排序算法

1、简介 排序是将元素按着指定关键字的大小递增或递减进行数据的排列&#xff0c;排序可以提高查找的效率 2、排序算法的分类 排序算法可大致分为四类七种&#xff0c;具体分类如下&#xff1a; 插入排序&#xff1a;直接插入排序、希尔排序 交换排序&#xff1a;冒泡排序、…

用.NET Core实现装饰模式和.NET Core的Stream简介

该文章综合了几本书的内容.某咖啡店项目的解决方案某咖啡店供应咖啡, 客户买咖啡的时候可以添加若干调味料, 最后要求算出总价钱.Beverage是所有咖啡饮料的抽象类, 里面的cost方法是抽象的. description变量在每个子类里面都需要设置(表示对咖啡的描述).每个子类实现cost方法, …

这个拖后腿的“in”

问题之源C# 7.2推出了全新的参数修饰符in&#xff0c;据说是能提升一定的性能&#xff0c;官方MSDN文档描述是&#xff1a;Add the in modifier to pass an argument by reference and declare your design intent to pass arguments by reference to avoid unnecessary copyin…

Surging 微服务框架使用入门

前言本文非 Surging 官方教程&#xff0c;只是自己学习的总结。如有哪里不对&#xff0c;还望指正。 我对 surging 的看法我目前所在的公司采用架构就是类似与Surging的RPC框架&#xff0c;在.NET 4.0框架上搭建Socket RPC&#xff0c;通过分组轮询的方式调度RPC&#xff0c;经…

RabbitMQ教程C#版 - 工作队列

先决条件本教程假定RabbitMQ已经安装&#xff0c;并运行在localhost标准端口&#xff08;5672&#xff09;。如果你使用不同的主机、端口或证书&#xff0c;则需要调整连接设置。从哪里获得帮助如果您在阅读本教程时遇到困难&#xff0c;可以通过邮件列表联系我们。1.工作队列&…

IdentityServer4实战 - 基于角色的权限控制及Claim详解

一.前言大家好&#xff0c;许久没有更新博客了&#xff0c;最近从重庆来到了成都&#xff0c;换了个工作环境&#xff0c;前面都比较忙没有什么时间&#xff0c;这次趁着清明假期有时间&#xff0c;又可以分享一些知识给大家。在QQ群里有许多人都问过IdentityServer4怎么用Role…

11、java中的I/O流(1)

我对于流的理解是这样的&#xff0c;计算机的本质本来就是对输入的数据进行操作&#xff0c;然后将结果输出的一种工具&#xff0c;数据在各个数据源节点之间进行流动&#xff0c;感觉流就是对这种状态的一种抽象&#xff0c;一个数据流表示的就是一系列数据序列&#xff0c;ja…

ASP.NET Core 集成测试

集成测试集成测试&#xff0c;也叫组装测试或联合测试。在单元测试的基础上&#xff0c;将所有模块按照设计要求&#xff08;如根据结构图&#xff09;组装成为子系统或系统&#xff0c;进行集成测试。实践表明&#xff0c;一些模块虽然能够单独地工作&#xff0c;但并不能保证…

使用C#开发Android应用之WebApp

近段时间了解了一下VS2017开发安卓应用的一些技术&#xff0c;特地把C#开发WebApp的一些过程记录下来&#xff0c;欢迎大家一起指教、讨论&#xff0c;废话少说&#xff0c;是时候开始表演真正的技术了。。1、新建空白Android应用2、拖一个WebView控件进来3、打开模拟器Genymot…

ASP.NET Core依赖注入深入讨论

这篇文章我们来深入探讨ASP.NET Core、MVC Core中的依赖注入&#xff0c;我们将示范几乎所有可能的操作把依赖项注入到组件中。依赖注入是ASP.NET Core的核心&#xff0c;它能让您应用程序中的组件增强可测试性&#xff0c;还使您的组件只依赖于能够提供所需服务的某些组件。举…