DotNetty完全教程(二)
第一个DotNetty应用程序
准备工作
NuGet包介绍
DotNetty由九个项目构成,在NuGet中都是单独的包,可以按需引用,其中比较重要的几个是以下几个:
- DotNetty.Common 是公共的类库项目,包装线程池,并行任务和常用帮助类的封装
- DotNetty.Transport 是DotNetty核心的实现
- DotNetty.Buffers 是对内存缓冲区管理的封装
- DotNetty.Codes 是对编码器解码器的封装,包括一些基础基类的实现,我们在项目中自定义的协议,都要继承该项目的特定基类和实现
- DotNetty.Handlers 封装了常用的管道处理器,比如Tls编解码,超时机制,心跳检查,日志等,如果项目中没有用到可以不引用,不过一般都会用到
开始一个项目
- 新建一个解决方案
- 新建一个项目
- 到NuGet中引用 DotNetty.Common DotNetty.Transport DotNetty.Buffers
- 开始编写实例代码
编写测试程序
回声测试应用程序编写 源码下载
- 新建一个解决方案 名字叫NettyTest
- 新建一个项目 名字叫EchoServer
- 到NuGet中引用 DotNetty.Common DotNetty.Transport DotNetty.Buffers
- 新建一个类 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的同学难以理解的点:
- 问:EchoServerHandler 是干什么用的?回答:Netty帮我们封装了底层的通信过程让我们不需要再关心套接字等网络底层的问题,更加专注于处理业务,何为业务?就是数据来了之后我要怎么办,Handler就是一个处理数据的工厂,那么上面的Handler中我们做了什么事情呢?稍加分析就能发现,我们在接到消息之后打印在了控制台上,之后将消息再发送回去。
- 问:WriteAsync 是在干什么?Flush 又是在干什么?答:由于是初学,不灌输太多,大家现在只需要知道数据写入之后并不会直接发出去,Flush的时候才会发出去。
- 在自动生成的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();} }
这个程序中同样有很多需要解释的,但是对于初学者来说,先明白这些概念就好了:
- bootstrap是启动引导的意思,Netty中的bootstrap的意思就是启动一个网络应用程序,那在启动之前我们肯定需要设置很多参数,bootstrap可以接收参数,引导用户启动Netty应用。
- EventLoopGroup 是一系列EventLoop的集合
- EventLoop 就对应了一个选择器(选择器看上一节的图)
- 一个Channel都需要绑定到一个选择器(EventLoop)上
- 每一个选择器(EventLoop)和一个线程绑定
- 我们可以把Handler串起来处理数据,这个我们后面再讲,这里的做法是把Handler串到pipeline上。
- 再新建一个项目取名叫EchoClient
- 新建一个类 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基本一致,这里我们还是需要解释一些问题:
- SimpleChannelInboundHandler 继承自 ChannelHandlerAdapter,前者更强大的地方是对于资源的自动释放(这是一个伏笔)
- Read0方法在代码的注释中已经解释过了,有兴趣的同学可以看一下源码。这里我就不贴出来了
- ctx.WriteAsync(Unpooled.CopiedBuffer(msg));如果这里直接将msg发送出去,大家就会发现,实验失败了,这是为什么呢?简单解释就是因为引用计数器机制,IByteBuffer只能使用一次,而在我们使用Read0方法接收这个消息的时候,这个消息的引用计数就被归零了,这时候我们再次使用就会报出异常,所以这里需要将源消息再复制一份。当然,如果你使用的Read方法则不会有这样的问题。原则上来说,我们不应该存储指向任何消息的引用供未来使用,因为这些引用都会自动失效(意思就是消息收到了处理完就丢掉,消息不应该被长久保存)。
- 编写客户端引导程序
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我们还有很多很多要讲,相信你学了之后的知识以后,回过头来再看这个实例,会有恍然大悟的感觉。如果你坚持看完了文章并且敲了程序并且试验成功了,恭喜你,晚饭加个鸡腿,我们还有很多东西要学。
DotNetty完全教程(二)相关推荐
- 黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级)
原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(二) Cryptography Application Block (高级) 本章介绍的是企业库加密应用程序模块 ...
- Konstrukt PHP REST框架 教程二
Konstrukt PHP REST框架 教程二 入门 - 第2部分 在本教程中,我们假设你已经完成了第一个教程,因为它的基础上产生的代码从该. 谈判的Content-Type 在大多数情况下会发出一 ...
- Mac下Android studio 之NDK配置教程(二)
Mac下Android studio 之NDK配置教程(二) (一)简述 从上一篇NDK配置教程(一) 中,我 简单的阐述了MAC下NDK的基本解压和环境配置步骤. 本节我讲详细描述android s ...
- python elasticsearch 入门教程(二) ---全文搜索
python elasticsearch 入门教程(二) ---全文搜索 截止目前的搜索相对都很简单:单个姓名,通过年龄过滤.现在尝试下稍微高级点儿的全文搜索--一项 传统数据库确实很难搞定的任务. ...
- openlayers地图旋转_OpenLayers教程二:实现简单的地图显示
本文衔接上一篇文章:不睡觉的怪叔叔:OpenLayers教程二:实现简单的地图显示zhuanlan.zhihu.com 经过上一篇文章对OpenLayers的简单了解以后,现在让我们来实现一个简单的 ...
- 计算机java语言教程,计算机JAVA教程二讲Java语言基础知识.doc
计算机JAVA教程二讲Java语言基础知识 2.1简单数据类型 2.1.1 标识符和保留字 1.标识符 程序员对程序中的各个元素加以命名时使用的命名记号称为标识符(identifier).Java语言 ...
- Java 结构体之 JavaStruct 使用教程二 JavaStruct 用例分析
使用环境 前一篇在介绍 JavaStruct 类时指定了使用库使用环境为 Java 5 及以上,也即开发我们使用的 JDK 版本为1.5及以上就可以了.以下讲解的用例可以直接将 code 直接粘贴到 ...
- C#微信公众号开发系列教程二(新手接入指南)
此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可直接跳过,也欢迎大神吐槽. 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教 ...
- MIP开发教程(二) 使用MIP-CLI工具调试MIP网页
初始化 MIP 配置 新建一个 MIP 网页 编写 MIP 网页代码 校验 MIP 网页 调试 MIP 网页 1. 初始化 MIP 配置 首先在html目录下进行初始化 MIP 配置: $ mip i ...
- 手把手教从零开始在GitHub上使用Hexo搭建博客教程(二)-Hexo参数设置
前言 前文手把手教从零开始在GitHub上使用Hexo搭建博客教程(一)-附GitHub注册及配置介绍了github注册.git相关设置以及hexo基本操作. 本文主要介绍一下hexo的常用参数设置. ...
最新文章
- 20190226-利用序列化完成小型记账程序
- Java基础语法纯小白入门
- Py之tornado:tornado库的简介、安装、使用方法之详细攻略
- 云栖首日:智物智造宣布3年10亿目标
- 华为root工具_华为Mate9解锁后无法ROOT 需要手动刷入Recovery怎么办【解决方法】...
- 【HDU - 1873】 看病要排队(优先队列)
- java 难题_您可以避免的6种组织成长难题
- SpringCloudRibbon
- SQL Server 中WITH (NOLOCK)
- 高斯法求解线性方程组
- Java Socket设置timeout几种常用方式总结
- java中格林尼治时间的输出_Java中格林尼治时间和时间戳的相互转换
- mysql多条语句union_Mysql同时执行多个select语句——union
- 《莎士比亚》文本生成
- JQ...CSS3爱心飘落特效
- MER:综述高通量测序应用于病原体和害虫诊断
- 如何:创建 C# 控制台应用程序
- Python : Beautiful Soup修改文档树
- 短信群发的频率应该是多少
- 云计算介绍PPT2011-03版
热门文章
- Java实现 foreach循环
- 申请海外MBA如何写英文自荐信和推荐信
- python datetime.datetime.now_python 时间格式datetime.now
- 播放器上音频断续问题的原因(realmedia)
- ubuntu配置opencv c++开发环境
- 三个脚编码开关c语言,编码开关的原理及使用方法
- 解决Web项目中图片无法显示问题
- 如何快速找出单词的变位词
- spring mvc 多线程并发
- 10-QNX与Android双系统通讯之FDBUS(1)