DotNetty完全教程(二)

11 篇文章 76 订阅

第一个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
    {
        /// <summary>
        /// 因为服务器只需要响应传入的消息,所以只需要实现ChannelHandlerAdapter就可以了
        /// </summary>
        public class EchoServerHandler : ChannelHandlerAdapter
        {
            /// <summary>
            /// 每个传入消息都会调用
            /// 处理传入的消息需要复写这个方法
            /// </summary>
            /// <param name="ctx"></param>
            /// <param name="msg"></param>
            public override void ChannelRead(IChannelHandlerContext ctx, object msg)
            {
                IByteBuffer message = msg as IByteBuffer;
                Console.WriteLine("收到信息:" + message.ToString(Encoding.UTF8));
                ctx.WriteAsync(message);
            }
            /// <summary>
            /// 批量读取中的最后一条消息已经读取完成
            /// </summary>
            /// <param name="context"></param>
            public override void ChannelReadComplete(IChannelHandlerContext context)
            {
                context.Flush();
            }
            /// <summary>
            /// 发生异常
            /// </summary>
            /// <param name="context"></param>
            /// <param name="exception"></param>
            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<TcpServerSocketChannel>();
                    bootstrap.ChildHandler(new ActionChannelInitializer<IChannel>(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<IByteBuffer>
        {
            /// <summary>
            /// Read0是DotNetty特有的对于Read方法的封装
            /// 封装实现了:
            /// 1. 返回的message的泛型实现
            /// 2. 丢弃非该指定泛型的信息
            /// </summary>
            /// <param name="ctx"></param>
            /// <param name="msg"></param>
            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<TcpSocketChannel>()
                        .Handler(new ActionChannelInitializer<ISocketChannel>(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我们还有很多很多要讲,相信你学了之后的知识以后,回过头来再看这个实例,会有恍然大悟的感觉。如果你坚持看完了文章并且敲了程序并且试验成功了,恭喜你,晚饭加个鸡腿,我们还有很多东西要学。

  • 17
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答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开发中常用的技术,分别用于创建桌面应用程序和构建高性能的网络应用程序。它们可以相互结合使用,实现更复杂和强大的功能。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值