DotNetty关键概念及简单示例(基于NET5)
DotNetty关键概念及简单示例(基于NET5)
1.DotNetty 设计的关键
异步和事件驱动是Netty设计的关键。
1.1 核心组件
1.1.1 Channel
Channel:一个连接就是一个Channel。
Channel是Socket的封装,提供绑定,读,写等操作,降低了直接使用Socket的复杂性。Channel是Socket的抽象,可以被注册到一个EventLoop上,EventLoop相当于Selector,每一个EventLoop又有自己的处理线程。


1.1.2 回调
回调:通知的基础。
1.1.3 EventLoop
EventLoop
我们之前就讲过EventLoop这里回顾一下:
一个 EventLoopGroup 包含一个或者多个 EventLoop;
一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
一个 Channel 在它的生命周期内只注册于一个 EventLoop;
一个 EventLoop 可能会被分配给一个或多个 Channel。

1.1.4 ChannelHandler
ChannelHandler是处理数据的逻辑容器
ChannelInboundHandler是接收并处理入站事件的逻辑容器,可以处理入站数据以及给客户端以回复。
1.1.5 ChannelPipeline
ChannelPipeline是将ChannelHandler穿成一串的的容器。

1.1.6 编码器和解码器
编码器和解码器都实现了ChannelInboundHandler和 ChannelOutboundHandler接口用于处理入站或出站数据。
1.1.7 Bootstrap引导类
- Bootstrap用于引导客户端,ServerBootstrap用于引导服务器
- 客户端引导类只需要一个EventLoopGroup服务器引导类需要两个EventLoopGroup。但是在简单使用中,也可以公用一个EventLoopGroup。为什么服务器需要两个EventLoopGroup呢?是因为服务器的第一个EventLoopGroup只有一个EventLoop,只含有一个SeverChannel用于监听本地端口,一旦连接建立,这个EventLoop就将Channel控制权移交给另一个EventLoopGroup,这个EventLoopGroup分配一个EventLoop给Channel用于管理这个Channel。
1.1.8 AbstractByteBuffer IByteBuffer IByteBufferHolder
字节级操作,工控协议的话大多都是字节流,我们以前的方式就是拼,大概就是:对照协议这两个字节是什么,后四个字节表示什么意思。现在DotNetty提供了一个ByteBuffer来简化我们对于字节流的操作。适用工控协议。json格式的,但是底层还是字节流。
2 DotNetty Nuget包
DotNetty由九个项目构成,在NuGet中都是单独的包,可以按需引用,其中比较重要的几个是以下几个:
- DotNetty.Common 是公共的类库项目,包装线程池,并行任务和常用帮助类的封装
- DotNetty.Transport 是DotNetty核心的实现
- DotNetty.Buffers 是对内存缓冲区管理的封装
- DotNetty.Codes 是对编码器解码器的封装,包括一些基础基类的实现,我们在项目中自定义的协议,都要继承该项目的特定基类和实现
- DotNetty.Handlers 封装了常用的管道处理器,比如Tls编解码,超时机制,心跳检查,日志等,如果项目中没有用到可以不引用,不过一般都会用到
3 一个例子
3.1 服务端代码示例
3.1.1 服务端配置
配置成服务器管道,TcpServerSocketChannel,之所以配置成服务器管道原因是与客户端管道不同,服务器管道多了侦听服务。将服务端的逻辑处理代码Handler以pipeline形式添加到channel中去。
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System;
using System.Threading.Tasks;
namespace EchoServer
{
class Program
{
static async Task RunServerAsync()
{
IEventLoopGroup eventLoop;
eventLoop = new MultithreadEventLoopGroup();
try
{
// 服务器引导程序
var bootstrap = new ServerBootstrap();
bootstrap.Group(eventLoop);
bootstrap.Channel<TcpServerSocketChannel>()
// 保持长连接
.ChildOption(ChannelOption.SoKeepalive, true);
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();
}
}
3.1.2 服务端处理逻辑代码
接收连入服务端代码的客户端消息,并将此消息重新返回给客户端。
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();
}
}
}
3.2 客户端代码示例
3.2.1 客户端服务配置
配置需要连接的服务端ip地址及其端口号,并且配置客户端的处理逻辑代码以pipeline形式添加到channel中去。
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("192.168.1.11"), 3000));
Console.ReadLine();
await clientChannel.CloseAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
await group.ShutdownGracefullyAsync();
}
}
static void Main(string[] args) => RunClientAsync().Wait();
}
}
3.2.2 客户端处理逻辑代码
客户端连接成功后,向服务端发送消息,接受到服务端消息,对消息计数再发还给服务端。
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
using System;
using System.Collections.Generic;
using System.Text;
namespace EchoClient
{
public class EchoClientHandler : SimpleChannelInboundHandler<IByteBuffer>
{
public static int i=0;
/// <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)
{
i++;
Console.WriteLine($"Receive From Server {i}:" + 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($"发送客户端消息");
context.WriteAndFlushAsync(Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes($"客户端消息!")));
}
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
{
Console.WriteLine(exception);
context.CloseAsync();
}
}
}
4 最终输出效果

备注:红框表示接收到消息的序号。
5 参考博客
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.cnblogs.com/JerryMouseLi/p/14086199.html
DotNetty关键概念及简单示例(基于NET5)的更多相关文章
- thrift简单示例 (基于C++)
这个thrift的简单示例, 来源于官网 (http://thrift.apache.org/tutorial/cpp), 因为我觉得官网的例子已经很简单了, 所以没有写新的示例, 关于安装的教程, ...
- Android HTTPS(1)概念和简单示例
Security with HTTPS and SSL The Secure Sockets Layer (SSL)—now technically known as Transport Layer ...
- 基于.NET CORE微服务框架 -surging的介绍和简单示例 (开源)
一.前言 至今为止编程开发已经11个年头,从 VB6.0,ASP时代到ASP.NET再到MVC, 从中见证了.NET技术发展,从无畏无知的懵懂少年,到现在的中年大叔,从中的酸甜苦辣也只有本人自知.随着 ...
- 最简单的基于DirectShow的示例:获取Filter信息
===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...
- 最简单的基于DirectShow的示例:视频播放器
===================================================== 最简单的基于DirectShow的示例文章列表: 最简单的基于DirectShow的示例:视 ...
- 最简单的基于FFmpeg的libswscale的示例(YUV转RGB)
===================================================== 最简单的基于FFmpeg的libswscale的示例系列文章列表: 最简单的基于FFmpeg ...
- 最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)
===================================================== 最简单的基于libRTMP的示例系列文章列表: 最简单的基于librtmp的示例:接收(RT ...
- 最简单的基于librtmp的示例:发布(FLV通过RTMP发布)
===================================================== 最简单的基于libRTMP的示例系列文章列表: 最简单的基于librtmp的示例:接收(RT ...
- 最简单的基于librtmp的示例:接收(RTMP保存为FLV)
===================================================== 最简单的基于libRTMP的示例系列文章列表: 最简单的基于librtmp的示例:接收(RT ...
随机推荐
- 838. Push Dominoes —— weekly contest 85
Push Dominoes There are N dominoes in a line, and we place each domino vertically upright. In the be ...
- java 执行shell命令及日志收集避坑指南
有时候我们需要调用系统命令执行一些东西,可能是为了方便,也可能是没有办法必须要调用.涉及执行系统命令的东西,则就不能做跨平台了,这和java语言的初衷是相背的. 废话不多说,java如何执行shell ...
- npm--npm+gulp发布至私服,报错E503解决方案
由于项目共享组件库的需要,我们搭建了npm私有服务器,供本公司几个项目可以访问.组件库使用gulp+webpack+npm进行打包构建,私服使用的是 Verdaccio直接搭建的,一键式傻瓜搭建,贼好 ...
- <摘自>飞:jxl简析2 [ http://www.emlog.net/fei ]
[<摘自>飞:jxl简析:http://www.emlog.net/fei] (二)应用 在进行实践前 , 我们需要对 excel 有一个大致的了解 ,excel 文件由一个工作簿 (Wo ...
- html中创建并调用vue组件的几种方法
最近在写项目的时候,总是遇到在html中使用vue.js的情况,且页面逻辑较多,之前的项目经验都是使用脚手架等已有的项目架构,使用.vue文件完成组价注册,及组件之间的调用,还没有过在html中创建组 ...
- 广度优先遍历&深度优先遍历
一.广度优先算法BFS(Breadth First Search) 基本实现思想 (1)顶点v入队列. (2)当队列非空时则继续执行,否则算法结束. (3)出队列取得队头顶点v: (4)查找顶点v的所 ...
- 信号发送接收函数:sigqueue/sigaction
信号是一种古老的进程间通信方式,下面的例子利用sigqueue发送信号并附带数据:sigaction函数接受信号并且处理时接受数据. 1.sigqueue: 新的信号发送函数,比kill()函数传递了 ...
- 源码分析:ReentrantReadWriteLock之读写锁
简介 ReentrantReadWriteLock 从字面意思可以看出,是和重入.读写有关系的锁,实际上 ReentrantReadWriteLock 确实也是支持可重入的读写锁,并且支持公平和非公平 ...
- JLC PCB 嘉立创自动确认生产稿,不讲武德?耗子尾汁!!!
首先,开局一张图,嘉立创又不做人的一天.嘉立创不讲武德,耗子尾汁!!! 之前下单,勾选了确定生产稿和不加客编,结果生产稿出来还是给我加了客编.那我出10元的意思何在?让我自己花3元看我花的10元有没有 ...
- pikachs 渗透测试1-环境及暴力破解
一.安装 PhpStudy20180211,默认安装 1.mysql默认密码是root,因为在虚拟机,保留不动 2.解压pikachs 到 C:\phpStudy\PHPTutorial\WWW\pi ...