DotNetty完全教程(二)

DotNetty完全教程(二)第一个 DotNetty 应用程序准备工作 NuGet 包介绍 DotNetty 由九个项目构成 在 NuGet 中都是单独的包 可以按需引用 其中比较重要的几个是以下几个 DotNetty Common 是公共的类库项目 包装线程池 并行任务和常用帮助类的封装 DotNetty Transport 是 DotNetty 核心的实现 DotNetty Buffers 是对内存缓冲区管理的封装 DotNett

第一个DotNetty应用程序

准备工作

NuGet包介绍

DotNetty由九个项目构成,在NuGet中都是单独的包,可以按需引用,其中比较重要的几个是以下几个:

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

开始一个项目

  1. 新建一个解决方案
  2. 新建一个项目
  3. 到NuGet中引用 DotNetty.Common DotNetty.Transport DotNetty.Buffers
  4. 开始编写实例代码

编写测试程序

回声测试应用程序编写 源码下载

  1. 新建一个解决方案 名字叫NettyTest
  2. 新建一个项目 名字叫EchoServer
  3. 到NuGet中引用 DotNetty.Common DotNetty.Transport DotNetty.Buffers
  4. 新建一个类 EchoServerHandler
    using DotNetty.Buffers; using DotNetty.Transport.Channels; using System; using System.Text; namespace EchoServer { ///  /// 因为服务器只需要响应传入的消息,所以只需要实现ChannelHandlerAdapter就可以了 ///  public class EchoServerHandler : ChannelHandlerAdapter { ///  /// 每个传入消息都会调用 /// 处理传入的消息需要复写这个方法 ///  ///  ///  public override void ChannelRead(IChannelHandlerContext ctx, object msg) { IByteBuffer message = msg as IByteBuffer; Console.WriteLine("收到信息:" + message.ToString(Encoding.UTF8)); ctx.WriteAsync(message); } ///  /// 批量读取中的最后一条消息已经读取完成 ///  ///  public override void ChannelReadComplete(IChannelHandlerContext context) { context.Flush(); } ///  /// 发生异常 ///  ///  ///  public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) { Console.WriteLine(exception); context.CloseAsync(); } } } 

    上面的代码注释已经非常详细了,相信看注释你就能明白这个类大致干了些什么,但是突如其来的一个类还是有点难以理解,那么本着认真负责的精神我会再详细解释一下没有学过Netty的同学难以理解的点:

    1. 问:EchoServerHandler 是干什么用的?回答:Netty帮我们封装了底层的通信过程让我们不需要再关心套接字等网络底层的问题,更加专注于处理业务,何为业务?就是数据来了之后我要怎么办,Handler就是一个处理数据的工厂,那么上面的Handler中我们做了什么事情呢?稍加分析就能发现,我们在接到消息之后打印在了控制台上,之后将消息再发送回去。
    2. 问:WriteAsync 是在干什么?Flush 又是在干什么?答:由于是初学,不灌输太多,大家现在只需要知道数据写入之后并不会直接发出去,Flush的时候才会发出去。
  5. 在自动生成的Program.cs中写入服务器引导程序。
    using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using DotNetty.Transport.Channels.Sockets; using System; using System.Threading.Tasks; namespace EchoServer { public class Program { static async Task RunServerAsync() { IEventLoopGroup eventLoop; eventLoop = new MultithreadEventLoopGroup(); try { // 服务器引导程序 var bootstrap = new ServerBootstrap(); bootstrap.Group(eventLoop); bootstrap.Channel 
        
          (); bootstrap.ChildHandler(new ActionChannelInitializer 
         
           (channel => { IChannelPipeline pipeline = channel.Pipeline; pipeline.AddLast(new EchoServerHandler()); })); IChannel boundChannel = await bootstrap.BindAsync(3000); Console.ReadLine(); await boundChannel.CloseAsync(); } catch (Exception ex) { Console.WriteLine(ex); } finally { await eventLoop.ShutdownGracefullyAsync(); } } static void Main(string[] args) => RunServerAsync().Wait(); } } 
          
        

    这个程序中同样有很多需要解释的,但是对于初学者来说,先明白这些概念就好了:

    1. bootstrap是启动引导的意思,Netty中的bootstrap的意思就是启动一个网络应用程序,那在启动之前我们肯定需要设置很多参数,bootstrap可以接收参数,引导用户启动Netty应用。
    2. EventLoopGroup 是一系列EventLoop的集合
    3. EventLoop 就对应了一个选择器(选择器看上一节的图)
    4. 一个Channel都需要绑定到一个选择器(EventLoop)上
    5. 每一个选择器(EventLoop)和一个线程绑定
    6. 我们可以把Handler串起来处理数据,这个我们后面再讲,这里的做法是把Handler串到pipeline上。
  6. 再新建一个项目取名叫EchoClient
  7. 新建一个类 EchoClientHandler
    using DotNetty.Buffers; using DotNetty.Transport.Channels; using System; using System.Text; namespace EchoClient { public class EchoClientHandler : SimpleChannelInboundHandler 
        
          { /// 
          /// Read0是DotNetty特有的对于Read方法的封装 /// 封装实现了: /// 1. 返回的message的泛型实现 /// 2. 丢弃非该指定泛型的信息 ///  /// 
          ///  protected override void ChannelRead0(IChannelHandlerContext ctx, IByteBuffer msg) { if (msg != null) { Console.WriteLine("Receive From Server:" + msg.ToString(Encoding.UTF8)); } ctx.WriteAsync(Unpooled.CopiedBuffer(msg)); } public override void ChannelReadComplete(IChannelHandlerContext context) { context.Flush(); } public override void ChannelActive(IChannelHandlerContext context) { Console.WriteLine("发送Hello World"); context.WriteAndFlushAsync(Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes("Hello World!"))); } public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) { Console.WriteLine(exception); context.CloseAsync(); } } }  
        

    Handler的编写方法于上面服务器的Handler基本一致,这里我们还是需要解释一些问题:

    1. SimpleChannelInboundHandler 继承自 ChannelHandlerAdapter,前者更强大的地方是对于资源的自动释放(这是一个伏笔)
    2. Read0方法在代码的注释中已经解释过了,有兴趣的同学可以看一下源码。这里我就不贴出来了
    3. ctx.WriteAsync(Unpooled.CopiedBuffer(msg));如果这里直接将msg发送出去,大家就会发现,实验失败了,这是为什么呢?简单解释就是因为引用计数器机制,IByteBuffer只能使用一次,而在我们使用Read0方法接收这个消息的时候,这个消息的引用计数就被归零了,这时候我们再次使用就会报出异常,所以这里需要将源消息再复制一份。当然,如果你使用的Read方法则不会有这样的问题。原则上来说,我们不应该存储指向任何消息的引用供未来使用,因为这些引用都会自动失效(意思就是消息收到了处理完就丢掉,消息不应该被长久保存)。
  8. 编写客户端引导程序
    using DotNetty.Transport.Bootstrapping; using DotNetty.Transport.Channels; using DotNetty.Transport.Channels.Sockets; using System; using System.Net; using System.Threading.Tasks; namespace EchoClient { class Program { static async Task RunClientAsync() { var group = new MultithreadEventLoopGroup(); try { var bootstrap = new Bootstrap(); bootstrap .Group(group) .Channel 
        
          () .Handler(new ActionChannelInitializer 
         
           (channel => { IChannelPipeline pipeline = channel.Pipeline; pipeline.AddLast(new EchoClientHandler()); })); IChannel clientChannel = await bootstrap.ConnectAsync(new IPEndPoint(IPAddress.Parse("10.10.10.158"), 3000)); Console.ReadLine(); await clientChannel.CloseAsync(); } catch (Exception ex) { Console.WriteLine(ex); } finally { await group.ShutdownGracefullyAsync(); } } static void Main(string[] args) => RunClientAsync().Wait(); } } 
          
        

写在最后

项目的完整代码我放在了码云上,你可以点击这里可以下载。我相信很多完全没有接触过Netty的同学在跟着写完了第一个项目之后还是很懵,虽然解释了很多,但是还是感觉似懂非懂,这很正常。就如同我们写完HelloWorld之后,仍然会纠结一下static void Main(string[] args)为什么要这么写。我要说的是,只要坚持写完了第一个应用程序,你就是好样的,关于Netty我们还有很多很多要讲,相信你学了之后的知识以后,回过头来再看这个实例,会有恍然大悟的感觉。如果你坚持看完了文章并且敲了程序并且试验成功了,恭喜你,晚饭加个鸡腿,我们还有很多东西要学。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/202241.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月20日 上午7:34
下一篇 2026年3月20日 上午7:34


相关推荐

  • [AI智能体与提效-137] – AI 智能体(Agent)应用程序开发主要平台全景图

    [AI智能体与提效-137] – AI 智能体(Agent)应用程序开发主要平台全景图

    2026年3月15日
    2
  • 一篇文章牢记C/C++指针和引用区别

    一篇文章牢记C/C++指针和引用区别指针有自己的一块空间 而引用只是一个别名 本质 使用 sizeof 看一个指针的大小是 4 32 位下 而引用则是被引用对象的大小 大小 指针可以被初始化为 NULL 而引用必须被初始化且必须是一个已有对象的引用 初始化 作为参数传递时 指针需要被解引用才可以对对象进行操作 而直接对引用的修改都会改变引用所指向的对象 可以有 const 指针 但是没有 const 引用 指针在使用中可以指向其它对象 但是引用只能是一个对象的引用 不能被改变 指针可以有多级指针 p 而引用至于一级 指针和

    2026年3月18日
    2
  • php – 通过curl从url获取JSON数据「建议收藏」

    php – 通过curl从url获取JSON数据

    2022年2月10日
    46
  • 了解VoIP技术

    了解VoIP技术VoIP VoiceoverIP 是现阶段 IT 行业和电信行业一个闪亮的名词 我们从字面上就可以大概知道它是解决什么问题的技术 简单地说 VoIP 的基本原理就是通过语音压缩的设备对我们的话音进行压缩编码处理 然后把这些语音数据根据相关协议进行打包 经过 IP 网络把数据包传输到目的地 再把这些语音数据包串起来 经过解码解压处理后 恢复成原来的语音信号 从而达到由 IP 网络传送话音的目的

    2026年3月20日
    2
  • Modelsim的安装教程

    Modelsim的安装教程提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、Modelsim安装二、激活成功教程1.拷贝Crack文件夹中的文件2.激活成功教程过程可能出现的错误前言Modelsim的安装与激活成功教程使用一、Modelsim安装打开下在之后的文件夹,直接双击exe文件进行安装。不熟悉时,可以直接使用默认路径进行安装,不进行路径上的修改。1、下载并解压好文件包,然后运行安装程序根据向导提示进行软件安装2、依提示安装软件过程中需要注意的是,会有三个弹出框提示,首先是是否创建桌面快捷方式提示

    2022年6月16日
    85
  • Jlink或者stlink用于SWD接口下载程序

    Jlink或者stlink用于SWD接口下载程序最近要使用stm32f103c8t6最小系统板,直接ISP串口下载程序太麻烦,就想着使用swd接口来调试。结果:通过SWD接口下载程序成功,但调试失败,还不知原因,会的的人麻烦交流一下。SWD接口:3.3VDIO(数据)CLK(时钟)GND1.首先声明jlink和stlink都有jtag和swd调试功能。jlink接口如下:如图,我使用的就是VCC…

    2022年4月25日
    53

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号