一、概要

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

Dotnetty是什么,原本Netty是由JBOSS提供的一个java开源框架后来由微软抄了一份.net的版本, 是业界最流行的NIO框架,整合了多种协议( 包括FTP、SMTP、 HTTP等各种二进制文本协议)的实现经验,精心设计的框架,在多个大型商业项目中得到充分验证。

个人使用感受如下:

1.Dotnetty各方面封装的很好,不要开发者过度关系细节。除了消息协议处理方面(socket网络通信的分包粘包处理)。

2.使用非常便捷,语法、和各组件结构清晰可重用性高。

3.也是.net core中为数不多看起来比较靠谱的框架,为什么会这么说呢在做股票相关项目时需求调研和技术选型的时候看了很多网络通信框架。

要么就是.Net Framework的版本,就怕有些开源团队不持续更新导致致命bug解决成本高等等问题。

二、简介

本篇文章主要围绕dotnetty基础概念和相关知识点来讲:

1. NIO和BIO的概念

2. 相关网络知识

socket交互流程 , 字节序和网络字节序

3. Dotnetty 框架介绍

4. Dotnetty Demo的讲解

三、主要内容

NIO和BIO、AIO的概念(摘抄自:https://zhuanlan.zhihu.com/p/111816019

  • BIO(同步阻塞):客户端在请求数据的过程中,保持一个连接,不能做其他事情。

  • NIO(同步非阻塞):客户端在请求数据的过程中,不用保持一个连接,不能做其他事情。(不用保持一个连接,而是用许多个小连接,也就是轮询)

  • AIO(异步非阻塞):客户端在请求数据的过程中,不用保持一个连接,可以做其他事情。(客户端做其他事情,数据来了等服务端来通知。)

Dotnetty 框架介绍

目前个人使用下来,主要用到的核心内容如上。

DotNetty.Common 是公共的类库项目,包装线程池,并行任务和常用帮助类的封装
DotNetty.Transport 是DotNetty核心的实现例如:Bootstrapping程序引导类 ,Channels 管道类(socket每有一个连接客户端就会创建一个channne)等等
DotNetty.Buffers 是对内存缓冲区管理的封装(主要在接收和发出是对socket通讯内容进行缓存管理)
DotNetty.Codes 是对编码器解码器的封装,包括一些基础基类的实现,我们在项目中自定义的协议,都要继承该项目的特定基类和实现(该类库在整个通讯环节是重中之重)
DotNetty.Handlers 封装了常用的管道处理器,比如Tls编解码,超时机制,心跳检查,日志等。(企业级开发中必不可少的处理类)

Dotnetty Demo的讲解

源码及演示代码都在官方github上: https://github.com/Azure/DotNetty

开发参考文档:https://netty.io/wiki/index.html (开发文档是java的版本,dotnetty都是对着java抄的会有不一样的地方但是大部分都相同。目前没有看到比较权威.net版本的文档)

下面主要分为两个部分去讲解:

Server部分


// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Echo.Server
{
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using DotNetty.Codecs;
using DotNetty.Handlers.Logging;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using DotNetty.Transport.Libuv;
using Examples.Common;


class Program
{
static async Task RunServerAsync()
{
ExampleHelper.SetConsoleLogger();


IEventLoopGroup bossGroup;//主要工作组,设置为2个线程
IEventLoopGroup workerGroup;//子工作组,推荐设置为内核数*2的线程数


if (ServerSettings.UseLibuv)
{
var dispatcher = new DispatcherEventLoopGroup();
bossGroup = dispatcher;
workerGroup = new WorkerEventLoopGroup(dispatcher);
}
else
{
bossGroup = new MultithreadEventLoopGroup(1);//主线程只会实例化一个
workerGroup = new MultithreadEventLoopGroup();//子线程组可以按照自己的需求在构造函数里指定数量
}


X509Certificate2 tlsCertificate = null;
if (ServerSettings.IsSsl)//是否使用ssl套接字加密
{
tlsCertificate = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
}
try
{
/*
*ServerBootstrap是一个引导类,表示实例化的是一个服务端对象
*声明一个服务端Bootstrap,每个Netty服务端程序,都由ServerBootstrap控制,
*通过链式的方式组装需要的参数
*/
var bootstrap = new ServerBootstrap();
//添加工作组,其中内部实现为将子线程组内置到主线程组中进行管理
bootstrap.Group(bossGroup, workerGroup);


if (ServerSettings.UseLibuv)//这个ifelse中实例化的是工作频道,就是处理读取或者发送socket数据的地方
{
bootstrap.Channel<TcpServerChannel>();
}
else
{
bootstrap.Channel<TcpServerSocketChannel>();
}


bootstrap
.Option(ChannelOption.SoBacklog, 100)
.Option(ChannelOption.SoReuseport, true)//设置端口复用
.Handler(new LoggingHandler("SRV-LSTN"))//初始化日志拦截器
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>//初始化Tcp服务
{
/*
* 这里主要是配置channel中需要被设置哪些参数,以及channel具体的实现方法内容。
* channel可以理解为,socket通讯当中客户端和服务端的连接会话,会话内容的处理在channel中实现。
*/


IChannelPipeline pipeline = channel.Pipeline;
if (tlsCertificate != null)
{
pipeline.AddLast("tls", TlsHandler.Server(tlsCertificate));//添加ssl加密
}
pipeline.AddLast(new LoggingHandler("SRV-CONN"));
pipeline.AddLast("framing-enc", new LengthFieldPrepender(2));//Dotnetty自带的编码器,将要发送的内容进行编码然后发送
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2));//Dotnetty自带的解码器,将接受到的内容进行解码然后根据内容对应到业务逻辑当中


pipeline.AddLast("echo", new EchoServerHandler());//server的channel的处理类实现

}));


IChannel boundChannel = await bootstrap.BindAsync(ServerSettings.Port);//指定服务端的端口号,ip地址donetty可以自动获取到本机的地址。也可以在这里手动指定。


Console.ReadLine();


await boundChannel.CloseAsync();//关闭
}
finally
{
//关闭释放并退出
await Task.WhenAll(
bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));
}
}


static void Main() => RunServerAsync().Wait();
}
}

 

channel的实现细节(socket会话内容处理和业务逻辑都可以在这里处理)

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Echo.Server
{
using System;
using System.Text;
using DotNetty.Buffers;
using DotNetty.Transport.Channels; /// <summary>
/// 该类为Server的Channel具体和定义实现
/// </summary>
public class EchoServerHandler : 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);//这里官方的例子是直接将客户端发送的内容原样返回给客户端,WriteAsync()是讲要发送的内容写入到数据流的缓存中。如果不想进入数据流可以直接调用WirteAndFlusAsync()写好了直接发送
} /// <summary>
/// 该次会话读取完成后回调函数
/// </summary>
/// <param name="context"></param>
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();//将WriteAsync写入的数据流缓存发送出去 /// <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);
}
}
}

Client部分:

其他未注释部分与服务端的解释一样

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. namespace Echo.Client
{
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using DotNetty.Codecs;
using DotNetty.Handlers.Logging;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using Examples.Common; class Program
{
static async Task RunClientAsync()
{
ExampleHelper.SetConsoleLogger(); var group = new MultithreadEventLoopGroup();//客户端与服务端不同的是,只需要一个主工作组进行工作协调即可不需要创建子线程组 X509Certificate2 cert = null;
string targetHost = null;
if (ClientSettings.IsSsl)
{
cert = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
targetHost = cert.GetNameInfo(X509NameType.DnsName, false);
}
try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(group)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.TcpNodelay, true)//设置为true的话不允许延迟直接发出,因为dotnetty内部实现中会将消息积累到一定的字节之后才发出。
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline; if (cert != null)
{
pipeline.AddLast("tls", new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(targetHost)));
}
pipeline.AddLast(new LoggingHandler());//donetty框架内部日志
pipeline.AddLast("framing-enc", new LengthFieldPrepender());
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, , , , )); pipeline.AddLast("echo", new EchoClientHandler());//client的channel的处理类实现
})); IChannel clientChannel = await bootstrap.ConnectAsync(new IPEndPoint(ClientSettings.Host, ClientSettings.Port));//设置服务端的端口号和ip地址 Console.ReadLine(); await clientChannel.CloseAsync();
}
finally
{
await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(), TimeSpan.FromSeconds());
}
} static void Main() => RunClientAsync().Wait();
}
}

废话不多说官方的demo源码看过了接下来看看效果,启动顺序为1.服务端-->2.客户端

如果出现以上情况,则代表服务端启动成功了。接下来启动客户端这时候有小伙伴会纳闷我已经F5 vs已经跑起来了服务端这时候客户端如何开启呢下图所示

运行效果:

到这里大致我们对dotnetty的框架有个初步的认识,后面的文章中将会逐渐加深对这套框架的理解并写实战项目以供大家学习。如果有想看教学视频的可以在博客的下方留言如果留言人数较多则考虑在b站放出教学视频更新频率也会更高。

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

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

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

    一.概要 在上一篇文章讲到Dotnetty的基本认识,本文这次会讲解dotnetty非常核心的模块是属于比较硬核的干货了,然后继续往下讲解如何根据自己的需求或者自己的喜好去配置Dotnetty而不是生 ...

  2. Spring实战第一章学习笔记

    Spring实战第一章学习笔记 Java开发的简化 为了降低Java开发的复杂性,Spring采取了以下四种策略: 基于POJO的轻量级和最小侵入性编程: 通过依赖注入和面向接口实现松耦合: 基于切面 ...

  3. activiti实战--第一章--认识Activiti

    学习资料:<Activiti实战> 第一章 认识Activiti 内容概览:讲解activiti的特点.接口概览.架构等基本信息. 1.3 Activiti的特点 1.使用mybatis ...

  4. Spring3实战第一章 Aop 切面 XML配置

    刚看spring3实战书籍第一章  切面以前没有关注过 现在看到了  随手试验一下 AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Objec ...

  5. 2017.2.20 activiti实战--第一章--认识Activiti

    学习资料:<Activiti实战> 第一章 认识Activiti 内容概览:讲解activiti的特点.接口概览.架构等基本信息. 1.3 Activiti的特点 1.使用mybatis ...

  6. 学习笔记-[Maven实战]-第一章:Maven简介

    Maven简介: Maven 可翻译为:知识的积累,也可以翻译为"专家"或"内行". Maven 是一个跨平台的项目管理工具,是Apache组织中一个很成功的开 ...

  7. DirectX12 3D 游戏开发与实战第一章内容

    DirectX12 3D 第一章内容 学习目标 1.学习向量在几何学和数学中的表示方法 2.了解向量的运算定义以及它在几何学中的应用 3.熟悉DirectXMath库中与向量有关的类和方法 1.1 向 ...

  8. 核心系统命令实战 第一章Linux命令行简介

    第一章Linux命令行简介 1.1 Linux命令行概述 1.1.1 Linux 命令行的开启和退出 开启:登陆账号密码进入系统 退出:exit/logout  快捷键:Ctrl+d 1.1.2 Li ...

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

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

随机推荐

  1. PDOStatement::errorInfo

    PDOStatement::errorInfo — 获取跟上一次语句句柄操作相关的扩展错误信息(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 array ...

  2. 一些Tips

    https://www.cnblogs.com/yeungchie/ 1. 快捷键e,有个EnableDimming选项,勾选后只会高亮你所选中的器件连线等等,其他器件亮度会下降,和mark不同,有利 ...

  3. 4.9 省选模拟赛 圆圈游戏 树形dp set优化建图

    由于圆不存在相交的关系 所以包容关系形成了树的形态 其实是一个森林 不过加一个0点 就变成了树. 考虑对于每个圆都求出最近的包容它的点 即他的父亲.然后树形dp即可.暴力建图n^2. const in ...

  4. 关于SqlServer那些事1(回归基础)

    即将实习,回归基础总结,希望可以再好好打磨一下基础的一些东西 关于如何在重新修改表结构时该变其权限设置 步骤: 点击工具 进入选项 设计器 取消勾选阻止保存要求重新创建表的更改 关于创建创建数据库以及 ...

  5. 数据结构C语言实现----快速排序

     快速排序算法 首先看下面这个例子: 我们取第一个元素为基准元素: 之后,从右边开始与基准元素挨个比较,如果比基准元素大,右指针往左移,如果比基准元素小,就与左指针指的元素交换(因为左指针永远停留在一 ...

  6. git使用-远程仓库(github为例)

    1.登录github(没有先注册账号) 2.settings>SSH and GPG keys>New SSH key Title(自己填写即可) key需要git命令生成 ssh-key ...

  7. vue中methods互相调用的方法

    a:function(goods) { this.aa= []; this.bb= 0; this.cc= 0; }, b:function(){ if(this.bbb!= 0){ this.aa= ...

  8. Springboot开启事务的支持

    主要分为两步 步骤一.在main方法加上@EnableTransactionManagement注解: @SpringBootApplication @EnableTransactionManagem ...

  9. Deep learning-based personality recognition from text posts of online social networks 阅读笔记

    文章目录 一.摘要 二.模型过程 1.文本预处理 1.1 文本切分 1.2 文本统一 2. 基于统计的特征提取 2.1 提取特殊的语言统计特征 2.2 提取基于字典的语言特征 3. 基于深度学习的文本 ...

  10. 在阿里云托管kubernetes上利用 cert-manager 自动签发 TLS 证书[无坑版]

    前言 排错的过程是痛苦的也是有趣的. 运维乃至IT,排错能力是拉开人与人之间的重要差距. 本篇会记录我的排错之旅. 由来 现如今我司所有业务都运行在阿里云托管kubernetes环境上,因为前端需要对 ...