Dotnetty搭建教程

文章介绍了Netty和DotNetty这两个高性能的网络应用程序框架,重点阐述了Netty的异步事件驱动模型和NIO的多路复用器如何提高性能。同时,详细展示了使用DotNetty进行服务端和客户端搭建的步骤,包括引入库、配置线程组、编码解码处理以及心跳机制的实现。此外,还讨论了粘包和拆包问题及其解决方案。
摘要由CSDN通过智能技术生成

记录

写本系列文章的目的主要是记录调研的成果。


一、Netty是什么

Netty 是一款用于创建高性能网络应用程序的高级框架。

Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端

二、DotNetty是什么

DotNetty是微软的Azure团队仿造Netty编写的网络应用程序框架。

三、BIO和NIO

3.1 以上图就是代表BIO的流程

  1. 建立连接要阻塞线程,读取数据要阻塞线程
  2. 如果要管理多个客户端,就需要为每个客户端建立不同的线程
  3. 会有大量的线程在休眠状态,等待接收数据,资源浪费
  4. 每个线程都要占用系统资源
  5. 线程的切换很耗费系统资源

 3.2 以上图就是NIO的流程

        Netty 的 IO 聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端连接。当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 I/O 阻塞导致的线程挂起,减少了线程数量导致的资源占用,减少了线程切换导致的资源消耗。

四、使用步骤

1.引入库

版本:.netframework4.6 ,DotNetty 0.6.0

引入:DotNetty.Buffers, DotNetty.Codecs ,  Dotnetty.Codecs.Mqtt ,DotNetty.Common , DotNetty.Handlers ,DotNetty.Transport

2.服务端

         服务端代码如下:


DiscardServerHandler dis= new DiscardServerHandler()
// 主工作线程组,设置为1个线程
var bossGroup = new MultithreadEventLoopGroup(1);
// 工作线程组,默认为内核数*2的线程数
var workerGroup = new MultithreadEventLoopGroup();//声明一个服务端Bootstrap,每个Netty服务端程序,都由ServerBootstrap控制,
//通过链式的方式组装需要的参数
var bootstrap = new ServerBootstrap();
     bootstrap
     .Group(bossGroup, workerGroup) // 设置主和工作线程组
     .Channel<TcpServerSocketChannel>() // 设置通道模式为TcpSocket
     .Option(ChannelOption.SoBacklog, 100) // 设置网络IO参数等,这里可以设置很多参数,当然你对网络调优和参数设置非常了解的话,你可以设置,或者就用默认参数吧
     .Option(ChannelOption.SoKeepalive, true)//保持连接
     //.Option(ChannelOption.RcvbufAllocator, new FixedRecvByteBufAllocator(4000))
    .ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel =>
                    {
                         byte[] messageBytes = Encoding.UTF8.GetBytes("^$end");
                     IByteBuffer initialMessage=Unpooled.Buffer(messageBytes.Length);
                            initialMessage.WriteBytes(messageBytes);
                        //工作线程连接器 是设置了一个管道,服务端主线程所有接收到的信息都会通过这个管道一层层往下传输
                        //同时所有出栈的消息 也要这个管道的所有处理器进行一步步处理
                        IChannelPipeline pipeline = channel.Pipeline;
                         pipeline.AddLast(new DelimiterBasedFrameDecoder(40000, initialMessage));//格式规则:每条数据必须以后缀名以^$end格式发送
                        pipeline.AddLast(new CommonServerDecoder());
                        pipeline.AddLast(new CommonServerEncoder());
                        pipeline.AddLast(new IdleStateHandler(0, 0, 180));
                        //业务handler ,这里是实际处理业务的Handler
                        pipeline.AddLast(dis);
                    }));

// bootstrap绑定到指定端口的行为 就是服务端启动服务,同样的Serverbootstrap可以bind到多个端口
IChannel boundChannel = await bootstrap.BindAsync(3399);

3.客户端

        客户端示例如下:

 private MultithreadEventLoopGroup group;
 private IChannel clientChannel;

group = new MultithreadEventLoopGroup();
                    var bootstrap = new Bootstrap();
                    bootstrap
                        .Group(group)
                        .Channel<TcpSocketChannel>()
                        .Option(ChannelOption.TcpNodelay, true)
                        //.Option(ChannelOption.RcvbufAllocator, new FixedRecvByteBufAllocator(40000)) 
                        .Handler(new ActionChannelInitializer<ISocketChannel>(c =>
                        {
                            byte[] messageBytes = Encoding.UTF8.GetBytes("^$end");
                            IByteBuffer initialMessage = Unpooled.Buffer(messageBytes.Length);
                            initialMessage.WriteBytes(messageBytes);
                            NettyClientHand nettyClientHand = new NettyClientHand();
                            IChannelPipeline pipeline = c.Pipeline;
                            pipeline.AddLast(new DelimiterBasedFrameDecoder(40000, initialMessage));
                            pipeline.AddLast(new IdleStateHandler(0, 0, 180));
                            pipeline.AddLast(new ClientDecoder());
                            pipeline.AddLast(new ClientEncoder());
                            pipeline.AddLast(nettyClientHand);
                        }));
                    clientChannel = await bootstrap.ConnectAsync(new IPEndPoint(ip, port));

4.粘包和拆包

        产生粘包和拆包问题的主要原因是在发送TCP数据的时候,底层会有一个缓冲区,例如1024个字节大小,如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题;如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。

5.粘包和拆包解决方案

        1.FixedLengthFrameDecoder :对于使用固定长度的粘包和拆包场景

        2.LineBasedFrameDecoder与DelimiterBasedFrameDecoder: 这里LineBasedFrameDecoder的作用主要是通过换行符,即\n或者\r\n对数据进行处理;而DelimiterBasedFrameDecoder的作用则是通过用户指定的分隔符对数据进行粘包和拆包处理

        3.LengthFieldBasedFrameDecoder与LengthFieldPrepender:LengthFieldBasedFrameDecoder与LengthFieldPrepender需要配合起来使用,其实本质上来讲,这两者一个是解码,一个是编码的关系。它们处理粘拆包的主要思想是在生成的数据包中添加一个长度字段,用于记录当前数据包的长度。LengthFieldBasedFrameDecoder会按照参数指定的包长度偏移量数据对接收到的数据进行解码,从而得到目标消息体数据;而LengthFieldPrepender则会在响应的数据前面添加指定的字节数据,这个字节数据中保存了当前消息体的整体字节数据长度。 

        4.LengthFieldBasedFrameDecoder和LengthFieldPrepender:自定义编码器

以上使用第二种分隔符对数据进行粘包 和拆包处理。如果客户端发送消息后缀没有用分隔符,服务端就不会接收。

6.编码和解码

        1.服务端CommonServerDecoder解码类代码如下

class CommonServerDecoder: ByteToMessageDecoder
    {
        protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
        {
            byte[] array = new byte[input.ReadableBytes];
            input.GetBytes(input.ReaderIndex, array, 0, input.ReadableBytes);
            input.Clear();
            output.Add(array);
        }
    }

        2.服务端CommonServerEncoder编码类代码如下

class CommonServerEncoder : MessageToByteEncoder<string>
    {
        protected override void Encode(IChannelHandlerContext context, string message, IByteBuffer output)
        {
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);
            IByteBuffer initialMessage = Unpooled.Buffer(messageBytes.Length);
            initialMessage.WriteBytes(messageBytes);
            output.WriteBytes(initialMessage);
        }
    }

        3.客户端ClientDecoder编码类代码如下

 class ClientDecoder : ByteToMessageDecoder
    {
        protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
        {
            byte[] array = new byte[input.ReadableBytes];
            input.GetBytes(input.ReaderIndex, array, 0, input.ReadableBytes);
            input.Clear();
            output.Add(array);
        }
    }

        4.客户端ClientEncoder编码类代码如下

 class ClientEncoder : MessageToByteEncoder<string>
    {
        protected override void Encode(IChannelHandlerContext context, string message, IByteBuffer output)
        {
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);
            IByteBuffer initialMessage = Unpooled.Buffer(messageBytes.Length);
            initialMessage.WriteBytes(messageBytes);
            output.WriteBytes(initialMessage);
        }
    }

7. 业务代码

        1.业务类(服务端DiscardServerHandler,客户端NettyClientHand)继承ChannelHandlerAdapter类

        2.IsSharable 一个handler可以被多个通道共享

        3.HandlerAdded和HandlerRemoved 上线和下线事件

        4.ChannelActive和ChannelInactive 通道活动和不活动事件

        5.ChannelRead 接收事件

 public override void ChannelRead(IChannelHandlerContext context, object message)
        {
            //if (message is IByteBuffer buffer)
            //{
            //    Console.WriteLine($"IByteBuffer方式,从服务端接收:{buffer.ToString(Encoding.UTF8)}");
            //}
            if (message is byte[] o)
            {
                Console.WriteLine($"byte方式,从服务端接收:{Encoding.UTF8.GetString(o)}");
            }
        }

        6.UserEventTriggered心跳事件,在IdleStateHandler配置读和写是180秒。在180秒没有接收或者发送消息就会触发心跳机制。注意物理拔插网线不会走掉线事件需要用心跳机制处理。

 public override void UserEventTriggered(IChannelHandlerContext context, object evt)
        {
            if (evt is IdleStateEvent) 
            {
                IdleStateEvent e = evt as IdleStateEvent;
                if (e.State == IdleState.AllIdle)
                {
                   //读或者写
                }
                else if (e.State == IdleState.ReaderIdle)
                {    
                    //读

                }
                else if (e.State == IdleState.WriterIdle) { 
                    //写
                }
            }
           
        }

        7.WriteAndFlushAsync发送

                服务端发送

channelcontext = dis.channel;
channelcontext?.WriteAndFlushAsync(string.Format("{0}^$end",121345));

                客户端发送

clientChannel.WriteAndFlushAsync(string.Format("{0}^$end",message));

        8.CloseAsync关闭

                服务端关闭

boundChannel.CloseAsync();
 //释放工作组线程
await Task.WhenAll(bossGroup.ShutdownGracefullyAsync(),
  workerGroup.ShutdownGracefullyAsync());

              客户端关闭


await clientChannel.CloseAsync();
await group.ShutdownGracefullyAsync();

总结

以上就是DotNetty的搭建.

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: WinForm是一种用于Windows桌面应用程序开发的GUI框架,而DotNetty是一个基于C#的异步网络应用程序框架。它们之间有一些不同和联系。 首先,两者的定位不同。WinForm主要用于开发面向桌面用户的应用程序,可以提供丰富的用户界面和用户交互功能。而DotNetty则是专注于网络应用程序的开发,可以实现高性能的、异步的网络通信。 其次,两者的开发方式也有所不同。WinForm是基于Windows窗体的开发方式,开发者通过拖放控件、设置属性和事件处理等方式来实现应用程序。而DotNetty则是通过编写C#代码来实现各种网络功能,例如TCP/IP通信、WebSocket通信等。 此外,DotNetty还提供了一些额外的功能和优势。由于它是异步的,可以提供更好的性能和响应速度。它也支持开发分布式的、高并发的服务端应用程序,可以应用于实时通信、即时消息等场景。另外,DotNetty还支持SSL/TLS加密通信,能够提供更高的安全性。 综上所述,WinForm和DotNetty在功能和用途上有一定的差异,但它们也可以相互配合使用。例如,可以使用WinForm来开发一个具有用户界面的客户端应用程序,然后使用DotNetty来实现与服务器的异步网络通信。这样可以既能享受到WinForm的便利性,又能利用DotNetty的高性能和强大的网络功能。 ### 回答2: WinForm和DotNetty都是与C#语言和.NET框架相关的技术。 WinForm是一种用于创建Windows桌面应用程序的技术。它是基于Microsoft .NET框架的一部分,提供了一组用于创建用户界面和处理用户输入的类和工具。通过使用WinForm,开发者可以快速创建可视化的桌面应用程序,并使用丰富的控件和功能来满足用户的需求。WinForm应用程序可以在Windows操作系统上运行,并可以通过.NET框架进行部署和维护。 而DotNetty是一个用于构建异步的,事件驱动的网络应用程序的开源框架。它基于C#语言和.Net标准库,提供了高效的网络通信功能,并支持TCP、UDP、HTTP等协议。DotNetty采用了异步和事件驱动的设计模式,可以处理大量的并发连接和请求。它提供了一些高级特性,如流水线处理、心跳检测、SSL加密等,使网络应用程序的开发变得更加简单和灵活。 WinForm和DotNetty可以结合使用,以实现更复杂和强大的网络应用程序。通过将DotNetty集成到WinForm应用程序中,开发者可以实现实时的网络通信和数据交换,例如聊天应用程序、实时数据传输、远程管理等。通过利用DotNetty的高性能和异步处理能力,WinForm应用程序可以处理大量的并发连接和请求,提供更好的用户体验和系统性能。 总结起来,WinForm和DotNetty都是C#和.NET开发中常用的技术,分别用于创建桌面应用程序和构建高性能的网络应用程序。它们可以相互结合使用,实现更复杂和强大的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值