一、概要

本系列文章主要讲述由微软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. 如何让img自动适应div容器大小

    IMG样式 (横向拉伸,纵向自动匹配大小) width:100%; height:auto; (纵向拉伸,横向自动匹配大小) width:auto; height:100%; DIV样式(元素居中显示 ...

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

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

  3. 浅析FMT,CMT, SMT区别

    FMT(fine-grained multithreading)又叫交叉多线程或指令交错多线程 –       每个时钟周期都进行线程的切换,多个线程交替执行,同一个周期只从一个线程发射指令到功能部件 ...

  4. “随手记”开发记录day12

    就我们团队昨天的讨论,今天进行更改. 今天我们先简单的更改了之前的粉色背景图,因为用户反应总览界面的“总览”二字,是深粉色背景不太美观.进过多次更改之后使颜色变得更舒适.

  5. three.js 着色器材质之变量(二)

    上一篇郭先生在例子中用到了着色器变量中的uniform和varying.这篇继续结合例子将一下attribute变量,在使用过程中也发现由于three.js的版本迭代,之前的一些属性和参数已经发生了改 ...

  6. CSRF 学习笔记

    1:什么是CSRF: 假设有一个支付网站:www.xx.com 向小明同学付款1000元数据包: www.xx.com/pay.php?name=xiaoming&account=xxxx@q ...

  7. java List接口二

    一 ArrayList集合 ArrayList集合数据存储的结构是数组结构.元素增删慢,查找快,由于日常开发中使用最多的 功能为查询数据.遍历数据,所以ArrayList是最常用的集合. 许多程序员开 ...

  8. 痞子衡嵌入式:一种i.MXRT下从App中进入ROM串行下载模式的方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT下在App中利用ROM API进ISP/SDP模式的方法. 我们知道i.MXRT系列分为两大阵营:CM33内核的i.MXRT ...

  9. node mssql 无法连接sql server

    mssql无法连接sql server主要有两种原因: Sql server使用的是Windows身份验证 Sql server并没有打开网络连接功能 1.打开Sql Server身份验证 参考这篇文 ...

  10. Python 用load_workbook 读取excel某个单元格数据、读取excel行数、列数

    from openpyxl import load_workbook path = r'D:\pywork\12' # EXCEL信息所在文件夹 e= load_workbook(path + '/' ...