一、概要

在上一篇文章讲到Dotnetty的基本认识,本文这次会讲解dotnetty非常核心的模块是属于比较硬核的干货了,然后继续往下讲解如何根据自己的需求或者自己的喜好去配置Dotnetty而不是生搬硬套官网的示例源码。如果看了本文有收获的话麻烦关注一下文章尾部的公众号和技术讨论群。各位的支持是对我莫大的帮助。

二、简介

主要讲解一下几个知识点:

  • EventLoopGroup & EventLoop
  • Bootstrap
  • Channel
  • ChannelPipeline & ChannelHandler
  • ChannelHandlerContext
  • ChannelHandler

三、详细内容

1.EventLoopGroup & EventLoop

  • 高性能RPC框架的3个要素:IO模型、数据协议、线程模型
  • EventLoop好比一个线程,1个EventLoop可以服务多个Channel,1个Channel只有一个EventLoop可以创建多个 EventLoop 来优化资源利用,也就是EventLoopGroup。
  • EventLoopGroup 负责分配 EventLoop 到新创建的 Channel,里面包含多个EventLoop
  • EventLoopGroup →多个 EventLoop ,EventLoop →维护一个 Selector。

2.服务器启动引导类:ServerBootstrap

Group :设置线程组模型,Reactor线程模型对比EventLoopGroup

  • 单线程

  • 多线程

  • 主从线程

Channel:设置channel通道类型NioServerSocketChannel、OioServerSocketChannel

Option: 作用于每个新建立的channel,设置TCP连接中的一些参数,如下:

  • ChannelOption.SO_BACKLOG: 存放已完成三次握手的请求的等待队列的最大长度;

  • ChannelOption.TCP_NODELAY: 为了解决Nagle的算法问题,默认是false, 要求高实时性,有数据时马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数,就设置为false,会累积一定大小后再发送。

  • ChildOption: 作用于被accept之后的连接

  • ChildHandler: 用于对每个通道里面的数据处理

3.连接通道类:Channel

Channel: 客户端和服务端建立的一个连接通道(可以理解为一个channel就是一个socket连接) ChannelHandler: 负责Channel的逻辑处理 ChannelPipeline: 负责管理ChannelHandler的有序容器

关系: 一个Channel包含一个ChannelPipeline,所有ChannelHandler都会顺序加入到ChannelPipeline中 创建 Channel时会自动创建一个ChannelPipeline,每个Channel都有一个管理它的pipeline,这关联是永久 性的Channel当状态出现变化,就会触发对应的事件。

生命周期:

  • ChannelRegistered: channel注册到一个EventLoop

  • ChannelActive: 变为活跃状态(连接到了远程主机),可以接受和发送数据

  • ChannelInactive: channel处于非活跃状态,没有连接到远程主机

  • ChannelUnregistered: channel已经创建,但是未注册到一个EventLoop里面,也就是没有和Selector绑定

4.频道的内部实现 ChannelHandler & ChannelPipeline

  • ChannelInboundHandler:(入站) 处理输入数据和Channel状态类型改变,适配器。

  • ChannelInboundHandlerAdapter(适配器设计模式) 常用的:SimpleChannelInboundHandler

  • ChannelOutboundHandler:(出站) 处理输出数据,适配器 ChannelOutboundHandlerAdapter

  • ChannelPipeline: 好比厂里的流水线一样,可以在上面添加多个ChannelHanler,也可看成是一串

  • ChannelHandler 实例,拦截穿过 Channel 的输入输出 event, ChannelPipeline 实现了拦截器的一种高级形 式,使得用户可以对事件的处理以及ChannelHanler之间交互获得完全的控制权。

5.频道的内部实现 ChannelHandler & ChannelPipeline

ChannelHandlerContext是连接ChannelHandler和ChannelPipeline的桥梁,ChannelHandlerContext部分方法和Channel及ChannelPipeline重合。

  • 好比调用write方法Channel、ChannelPipeline、ChannelHandlerContext 都可以调用此方法,前两者都会在整个管道流里 传播,而ChannelHandlerContext就只会在后续的Handler里面传播。
  • AbstractChannelHandlerContext类双向链表结构,next/prev分别是后继节点,和前驱节点。
  • DefaultChannelHandlerContext 是实现类,但是大部分都是父类那边完成,这个只是简单的实现一些方法 主要就是判断Handler的类型。
  • ChannelInboundHandler之间的传递,主要通过调用ctx里面的FireXXX()方法来实现下个handler的调用。

6.Handler执行顺序

一般的项目中,inboundHandler和outboundHandler有多个,在Pipeline中的执行顺序?

InboundHandler顺序执行,OutboundHandler逆序执行

  • InboundHandler顺序执行,OutboundHandler逆序执行
  • InboundHandler之间传递数据,通过context.fireChannelRead(message)
  • InboundHandler通过context.write(message),则会传递到outboundHandler
  • 使用context.write(msg)传递消息,Inbound需要放在结尾,在Outbound之后,不然outboundhandler会不执行; 但是使用channel.write(msg)、pipline.write(msg)情况会不一致,都会执行。
  • OutBound和Inbound谁先执行,针对客户端和服务端而言,客户端是发起请求再接受数据,先outbound再 inbound,服务端则相反。

四、实战环节

以上概念性的东西介绍完了之后开始编写本章实战代码(完整的案例代码将在qq群文件共享里上传,文章末尾有QQ群二维码和联系方式)。接下来我们先看一下项目结构。

Handlers - 主要存放所有处理相关类。

Initializer - 存放初始化tcp服务的相关内容。

appsetting.json - 主要存放的内容为,服务端的相关配置例如:ip地址、端口号等。

dotnetty - 安全证书

Program - 启动类


项目结构介绍完毕之后,我大致将这个demo分为5个部分来实现具体根据自己需求去设计搭建结构都是可以的,这里的内容仅供参考。

  • 第一步,配置构建引导类

         //主要工作组,设置为2个线程
private static readonly IEventLoopGroup bossGroup = new MultithreadEventLoopGroup();
//子工作组,默认为内核数*2的线程数
private static readonly IEventLoopGroup workerGroup = new MultithreadEventLoopGroup(); static async Task RunAsync() {
/*
*初始化服务端引导对象。
*声明一个服务端Bootstrap,每个Netty服务端程序,都由ServerBootstrap控制,
*通过链式的方式组装需要的参数
*/
ServerBootstrap bootstrap = new ServerBootstrap();
//添加工作组
bootstrap.Group(bossGroup, workerGroup);
//初始化工作频道
bootstrap.Channel<TcpServerSocketChannel>();
bootstrap
//存放已完成三次握手的请求的等待队列的最大长度;
.Option(ChannelOption.SoBacklog, )
//ByteBuf的分配器(重用缓冲区)大小
.Option(ChannelOption.Allocator, UnpooledByteBufferAllocator.Default)
//接收字符的长度
.Option(ChannelOption.RcvbufAllocator, new FixedRecvByteBufAllocator( * ))
//保持长连接
.ChildOption(ChannelOption.SoKeepalive, true)
//取消延迟发送
.ChildOption(ChannelOption.TcpNodelay, true)
//端口复用
.ChildOption(ChannelOption.SoReuseport, true)
//初始化日志拦截器,可以不加
.Handler(new LoggingHandler("SRV-LSTN"))
//自定义初始化Tcp服务
.ChildHandler(new EchoServerInitializer()); //绑定服务端,端口号。IP地址默认读取项目配置文件。
await bootstrap.BindAsync(ServerSettings.Port);
}
  • 第二步,初始化Channel相关处理类
  /// <summary>
/// 初始化
/// </summary>
public class EchoServerInitializer : ChannelInitializer<ISocketChannel>
{
/// <summary>
/// No interaction time.300s
/// </summary>
public const int AllTimeOut = * ; /// <summary>
/// Read Time Out.60s
/// </summary>
public const int ReadTimeOut = ; /// <summary>
/// Recive Time Out.60s
/// </summary>
public const int WriterTimeOut = ; protected override void InitChannel(ISocketChannel channel)
{
/*
* 工作线程连接器是设置了一个频道,服务端主线程所有接收到的信息都会通过这个管道一层层往下传输
* 同时所有出栈的消息 也要这个频道的所有处理器进行一步步处理
*/
IChannelPipeline pipeline = channel.Pipeline;
//初始化Dotnetty日志拦截器
pipeline.AddLast(new LoggingHandler("SRV-CONN"));
//心跳超时时间配置
pipeline.AddLast(new IdleStateHandler(
ReadTimeOut,
WriterTimeOut,
AllTimeOut));
//消息内容编码逻辑处理类
pipeline.AddLast("encoder", new EncoderHandler());
//解码逻辑处理类
pipeline.AddLast("decoder", new DecoderHandler());
//心跳逻辑处理
pipeline.AddLast(new HeartBeatHandler());
//每个频道请求消息处理类
pipeline.AddLast(new ServerHandler());
}
}
  • 第三步,配置、实现心跳处理机制
  public class HeartBeatHandler : ChannelHandlerAdapter
{
/// <summary>
/// 每个频道都有自己的心跳管理,如果频道长时间不操作踢掉线的逻辑可以写在这里
/// </summary>
/// <param name="context"></param>
/// <param name="evt"></param>
public override void UserEventTriggered(IChannelHandlerContext context, object evt)
{
var eventState = evt as IdleStateEvent;
if (eventState != null)
{
String type = string.Empty;
if (eventState.State == IdleState.ReaderIdle)
{
type = "read idle";//没有任何接受
}
else if (eventState.State == IdleState.WriterIdle)
{
type = "write idle";//没有任何写入
}
else if (eventState.State == IdleState.AllIdle)
{
type = "all idle";
context.CloseAsync();//5分钟内无任何交互则断开该客户端连接
}
}
else
{
base.UserEventTriggered(context, evt);
}
}
}
  • 第四步,编码、解码
  /// <summary>
/// 解码
/// </summary>
public class DecoderHandler : ByteToMessageDecoder
{
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
throw new NotImplementedException();
}
} public class EncoderHandler : MessageToByteEncoder<byte[]>
{
/// <summary>
/// 编码
/// </summary>
/// <param name="context"></param>
/// <param name="message"></param>
/// <param name="output"></param>
protected override void Encode(IChannelHandlerContext context, byte[] message, IByteBuffer output)
{
throw new NotImplementedException();
}
}
  • 第五步,Channel逻辑处理实现
 public class ServerHandler : ChannelHandlerAdapter
{
/*
* Channel的生命周期
* 1.ChannelRegistered 先注册
* 2.ChannelActive 再被激活
* 3.ChannelRead 客户端与服务端建立连接之后的会话(数据交互)
* 4.ChannelReadComplete 读取客户端发送的消息完成之后
* error. ExceptionCaught 如果在会话过程当中出现dotnetty框架内部异常都会通过Caught方法返回给开发者
* 5.ChannelInactive 使当前频道处于未激活状态
* 6.ChannelUnregistered 取消注册
*/ /// <summary>
/// 频道注册
/// </summary>
/// <param name="context"></param>
public override void ChannelRegistered(IChannelHandlerContext context)
{
base.ChannelRegistered(context);
} /// <summary>
/// socket client 连接到服务端的时候channel被激活的回调函数
/// </summary>
/// <param name="context"></param>
public override void ChannelActive(IChannelHandlerContext context)
{
//一般可用来记录连接对象信息
base.ChannelActive(context);
} /// <summary>
/// socket接收消息方法具体的实现
/// </summary>
/// <param name="context">当前频道的句柄,可使用发送和接收方法</param>
/// <param name="message">接收到的客户端发送的内容</param>
public override void ChannelRead(IChannelHandlerContext context, object message)
{
var buffer = message as IByteBuffer;
if (buffer != null)
{
Console.WriteLine("Received from client: " + buffer.ToString(Encoding.UTF8));
}
context.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: " + exception);
context.CloseAsync();
} /// <summary>
/// 当前频道未激活状态
/// </summary>
/// <param name="context"></param>
public override void ChannelInactive(IChannelHandlerContext context)
{
base.ChannelInactive(context);
} /// <summary>
/// 取消注册当前频道,可理解为销毁当前频道
/// </summary>
/// <param name="context"></param>
public override void ChannelUnregistered(IChannelHandlerContext context)
{
base.ChannelUnregistered(context);
}
}

希望大家多多支持。不胜感激。

.NET Core3.1 Dotnetty实战第二章的更多相关文章

  1. .NET Core3.1 Dotnetty实战第一章

    一.概要 本系列文章主要讲述由微软Azure团队研发的.net的版本的netty,Dotnetty.所有的开发都将基于.net core 3.1版本进行开发. Dotnetty是什么,原本Netty是 ...

  2. #Spring实战第二章学习笔记————装配Bean

    Spring实战第二章学习笔记----装配Bean 创建应用对象之间协作关系的行为通常称为装配(wiring).这也是依赖注入(DI)的本质. Spring配置的可选方案 当描述bean如何被装配时, ...

  3. AS开发实战第二章学习笔记——其他

    第二章学习笔记(1.19-1.22)像素Android支持的像素单位主要有px(像素).in(英寸).mm(毫米).pt(磅,1/72英寸).dp(与设备无关的显示单位).dip(就是dp).sp(用 ...

  4. RxJava2实战--第二章 RxJava基础知识

    第二章 RxJava基础知识 1. Observable 1.1 RxJava的使用三步骤 创建Observable 创建Observer 使用subscribe()进行订阅 Observable.j ...

  5. .NET ORM框架HiSql实战-第二章-使用Hisql实现菜单管理(增删改查)

    一.引言 上一篇.NET ORM框架HiSql实战-第一章-集成HiSql 已经完成了Hisql的引入,本节就把 项目中的菜单管理改成hisql的方式实现. 菜单管理界面如图: 二.修改增删改查相关代 ...

  6. .NET Core3.1 Dotnetty实战第三章

    一.概要 本章主要内容就是讲解如何在dotnetty的框架中进行网络通讯以及编解码对象.数据包分包拆包的相关知识点. 后续会专门开一篇避坑的文章,主要会描述在使用dotnetty的框架时会遇到的哪些问 ...

  7. activiti实战--第二章--搭建Activiti开发环境及简单示例

    (一)搭建开发环境 学习资料:<Activiti实战> 第一章 认识Activiti 2.1 下载Activiti 官网:http://activiti.org/download.html ...

  8. 2017.2.20 activiti实战--第二章--搭建Activiti开发环境及简单示例(二)简单示例

    学习资料:<Activiti实战> 第一章 搭建Activiti开发环境及简单示例 2.5 简单流程图及其执行过程 (1)leave.bpmn 后缀名必须是bpmn.安装了activiti ...

  9. 2017.2.20 activiti实战--第二章--搭建Activiti开发环境及简单示例(一)搭建开发环境

    学习资料:<Activiti实战> 第一章 认识Activiti 2.1 下载Activiti 官网:http://activiti.org/download.html 进入下载页后,可以 ...

随机推荐

  1. 5.29 省选模拟赛 树的染色 dp 最优性优化

    LINK:树的染色 考场上以为这道题要爆蛋了 没想到 推出正解来了. 反正是先写了爆搜的 爆搜最近越写越熟练了 容易想到dp 容易设出状态 f[i][j]表示以i为根的子树内白色的值为j此时黑色的值怎 ...

  2. Nginx的文章推荐

    Nginx服务器之负载均衡策略(6种) Nginx与Tomcat实现请求动态数据与请求静态资源的分离 Nginx 相关介绍(Nginx是什么?能干嘛?)     https://www.cnblogs ...

  3. Spring 参数注入

    一个(类)Bean可能包含多种属性,这些属性怎么配置???  见下: 用People  Dog Cat Tiger  Panda五个类来学习一些常用的=_= 重点在XML <!--基本类型注入- ...

  4. 【FZYZOJ】数论课堂 题解(约数个数定理)

    前言:想了两个小时orz,最后才想到要用约数个数定理…… ------------- 题目大意: 给定$n,q,A[1],A[2],A[3]$ 现有$A[i]=(A[i-1]+A[i-2]+A[i-3 ...

  5. JSON 和 POJO 互转,List<T> 和 JSON 互转

    JSON 和 POJO import com.alibaba.fastjson.JSONObject; import org.slf4j.Logger; import org.slf4j.Logger ...

  6. 一个简单的CPP处理框架

    好久没有在csdn上写过东西了,这么多年,一方面是工作忙,下班到家也没有开过电脑了,要陪小孩玩: 下面分享一段代码,是用CPP做的一个简单的消息(协议)处理框架: 是通过成员函数指针+map来实现的: ...

  7. Codechef July Challenge 2020 Division 1 记录

    目录 Missing a Point Chefina and Swaps Doctor Chef Chef and Dragon Dens LCM Constraints Weird Product ...

  8. .NetCore(Avalonia) 项目dll混淆,Ubuntu 或者deepin操作系统 deb安装包解压,重新打包

    .NetCore(Avalonia) 项目dll混淆,deb安装包解压,重新打包 本文分为两部分,一部分是介绍使用 DotNetReactor6.0 及以上版本混淆.netcore项目的dll. 另一 ...

  9. 免费深度学习GPU,Google Yes!

    深度学习越加火热,但是,很多实验室并没有配套的硬件设备,让贫穷的学生党头大 经过网上大量的搜罗,我整理了适合学生党的深度学习解决方案.利用Colab + Kaggle两大免费的GPU环境,让深度学习变 ...

  10. 【Spring注解驱动开发】如何实现方法、构造器位置的自动装配?我这样回答让面试官很满意!

    在 冰河技术 微信公众号前面的文章中,我们介绍了如何使用注解来自动装配Spring组件.之前将的都是在来的字段上添加注解,那有没有什么方法可以实现方法.构造器位置的自动装配吗?今天我们就一起来探讨下如 ...