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 版權協議,轉載請附上原文出處鏈接和本聲明。 本文鏈接://www.cnblogs.com/JerryMouseLi/p/14086199.html