Kommunikation basierend auf DotNetty
DotNetty : Es handelt sich um eine Version von Netty, die vom Azure-Team von Microsoft mithilfe von C# implementiert wurde. Es ist eine hervorragende Netzwerkbibliothek für die .NET-Plattform.
Projekteinführung
OpenDeploy.Communication
Das Klassenbibliotheksprojekt ist eine kommunikationsbezogene Infrastrukturschicht
-
Codec
Das Modul implementiert die Kodierung und Dekodierung
-
Convention
Moduldefinitionskonventionen, wie abstrakter Geschäftshandler, NachrichtenträgerNettyMessage
, Nachrichtenkontext „NettyContext“ usw.
Benutzerdefiniertes Nachrichtenformat
Die Nachrichtenklasse ist NettyMessage
, die den Nachrichtenkopf NettyHeader
und den Nachrichtentext kapseltBody
NettyMessage
kapselt den Nachrichtenkopf
NettyHeader
und den NachrichtentextBody
NettyMessage klicken, um den Code anzuzeigen
/// <summary> Netty消息 </summary>
public class NettyMessage
{
/// <summary> 消息头 </summary>
public NettyHeader Header { get; init; } = default!;
/// <summary> 消息体(可空,可根据具体业务而定) </summary>
public byte[]? Body { get; init; }
/// <summary> 消息头转为字节数组 </summary>
public byte[] GetHeaderBytes()
{
var headerString = Header.ToString();
return Encoding.UTF8.GetBytes(headerString);
}
/// <summary> 是否同步消息 </summary>
public bool IsSync() => Header.Sync;
/// <summary> 创建Netty消息工厂方法 </summary>
public static NettyMessage Create(string endpoint, bool sync = false, byte[]? body = null)
{
return new NettyMessage
{
Header = new NettyHeader { EndPoint = endpoint, Sync = sync },
Body = body
};
}
/// <summary> 序列化为JSON字符串 </summary>
public override string ToString() => Header.ToString();
}
NettyHeader
Der Nachrichtenheader, einschließlich der eindeutigen Kennung der Anforderung, ob die Nachricht, der Endpunkt usw. synchronisiert werden sollen, wird bei der Datenübertragung in JSON serialisiert.
Klicken Sie auf NettyHeader, um den Code anzuzeigen
/// <summary> Netty消息头 </summary>
public class NettyHeader
{
/// <summary> 请求消息唯一标识 </summary>
public Guid RequestId { get; init; } = Guid.NewGuid();
/// <summary> 是否同步消息, 默认false是异步消息 </summary>
public bool Sync { get; init; }
/// <summary> 终结点 (借鉴MVC,约定为Control/Action模式) </summary>
public string EndPoint { get; init; } = string.Empty;
/// <summary> 序列化为JSON字符串 </summary>
public override string ToString() => this.ToJsonString();
}
-
Die eindeutige Kennung der Anforderungsnachricht
RequestId
wird zur eindeutigen Identifizierung der Nachricht verwendet. Sie wird hauptsächlich zum Senden synchroner Anforderungen verwendet, da die Standardnachricht asynchron ist. Senden Sie sie einfach ohne Warten auf eine Antwort a>
-
Ob Nachrichten synchronisiert werden sollen
Sync
ist nicht erforderlich, hauptsächlich zur Visualisierung und zum einfachen Debuggen
-
Endpunkt
EndPoint
(unter Berücksichtigung von Lehren aus MVC, vereinbart als Steuerungs-/Aktionsmodus), analysiert der Server direkt den entsprechenden Prozessor
Encoder
Klicken Sie auf DefaultEncoder, um den Code anzuzeigen
public class DefaultEncoder : MessageToByteEncoder<NettyMessage>
{
protected override void Encode(IChannelHandlerContext context, NettyMessage message, IByteBuffer output)
{
//消息头转为字节数组
var headerBytes = message.GetHeaderBytes();
//写入消息头长度
output.WriteInt(headerBytes.Length);
//写入消息头字节数组
output.WriteBytes(headerBytes);
//写入消息体字节数组
if (message.Body != null && message.Body.Length > 0)
{
output.WriteBytes(message.Body);
}
}
}
Decoder
Klicken Sie auf DefaultDecoder, um den Code anzuzeigen
public class DefaultDecoder : MessageToMessageDecoder<IByteBuffer>
{
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
//消息总长度
var totalLength = input.ReadableBytes;
//消息头长度
var headerLength = input.GetInt(input.ReaderIndex);
//消息体长度
var bodyLength = totalLength - 4 - headerLength;
//读取消息头字节数组
var headerBytes = new byte[headerLength];
input.GetBytes(input.ReaderIndex + 4, headerBytes, 0, headerLength);
byte[]? bodyBytes = null;
string? rawHeaderString = null;
NettyHeader? header;
try
{
//把消息头字节数组,反序列化为JSON
rawHeaderString = Encoding.UTF8.GetString(headerBytes);
header = JsonSerializer.Deserialize<NettyHeader>(rawHeaderString);
}
catch (Exception ex)
{
Logger.Error($"解码失败: {rawHeaderString}, {ex}");
return;
}
if (header is null)
{
Logger.Error($"解码失败: {rawHeaderString}");
return;
}
//读取消息体字节数组
if (bodyLength > 0)
{
bodyBytes = new byte[bodyLength];
input.GetBytes(input.ReaderIndex + 4 + headerLength, bodyBytes, 0, bodyLength);
}
//封装为NettyMessage对象
var message = new NettyMessage
{
Header = header,
Body = bodyBytes,
};
output.Add(message);
}
}
NettyServer-Implementierung
Klicken Sie auf NettyServer, um den Code anzuzeigen
public static class NettyServer
{
/// <summary>
/// 开启Netty服务
/// </summary>
public static async Task RunAsync(int port = 20007)
{
var bossGroup = new MultithreadEventLoopGroup(1);
var workerGroup = new MultithreadEventLoopGroup();
try
{
var bootstrap = new ServerBootstrap().Group(bossGroup, workerGroup);
bootstrap
.Channel<TcpServerSocketChannel>()
.Option(ChannelOption.SoBacklog, 100)
.Option(ChannelOption.SoReuseaddr, true)
.Option(ChannelOption.SoReuseport, true)
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
pipeline.AddLast("framing-enc", new LengthFieldPrepender(4));
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, 0, 4));
pipeline.AddLast("decoder", new DefaultDecoder());
pipeline.AddLast("encoder", new DefaultEncoder());
pipeline.AddLast("handler", new ServerMessageEntry());
}));
var boundChannel = await bootstrap.BindAsync(port);
Logger.Info($"NettyServer启动成功...{boundChannel}");
Console.ReadLine();
await boundChannel.CloseAsync();
Logger.Info($"NettyServer关闭监听了...{boundChannel}");
}
finally
{
await Task.WhenAll(
bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1))
);
Logger.Info($"NettyServer退出了...");
}
}
}
-
Schließlich haben wir
ServerMessageEntry
zur Serverpipeline als Einstiegspunkt für die Nachrichtenverarbeitung hinzugefügt
Klicken Sie auf ServerMessageEntry, um den Code anzuzeigen
public class ServerMessageEntry : ChannelHandlerAdapter
{
/// <summary> Netty处理器选择器 </summary>
private readonly DefaultNettyHandlerSelector handlerSelector = new();
public ServerMessageEntry()
{
//注册Netty处理器
handlerSelector.RegisterHandlerTypes(typeof(EchoHandler).Assembly.GetTypes());
}
/// <summary> 通道激活 </summary>
public override void ChannelActive(IChannelHandlerContext context)
{
Logger.Warn($"ChannelActive: {context.Channel}");
}
/// <summary> 通道关闭 </summary>
public override void ChannelInactive(IChannelHandlerContext context)
{
Logger.Warn($"ChannelInactive: {context.Channel}");
}
/// <summary> 收到客户端的消息 </summary>
public override async void ChannelRead(IChannelHandlerContext context, object message)
{
if (message is not NettyMessage nettyMessage)
{
Logger.Error("从客户端接收消息为空");
return;
}
try
{
Logger.Info($"收到客户端的消息: {nettyMessage}");
//封装请求
var nettyContext = new NettyContext(context.Channel, nettyMessage);
//选择处理器
AbstractNettyHandler handler = handlerSelector.SelectHandler(nettyContext);
//处理请求
await handler.ProcessAsync();
}
catch(Exception ex)
{
Logger.Error($"ServerMessageEntry.ChannelRead: {ex}");
}
}
}
-
Gemäß der Konvention wird die Klasse, die
AbstractNettyHandler
erbt, als Geschäftsprozessor betrachtet
-
ServerMessageEntry
Nachdem Sie die Nachricht erhalten haben, kapseln Sie sie zunächst alsNettyContext
, ähnlich wie HttpContext in MVC, das die Anforderungs- und Antwortobjekte kapselt und die Anforderung intern analysiertEndPoint
, aufgeteilt inHandlerName
,ActionName
-
DefaultNettyHandlerSelector
Bietet Methoden zum Registrieren von ProzessorenRegisterHandlerTypes
und Methoden zum Auswählen von ProzessorenSelectHandler
-
SelectHandler
, die Standardregel besteht darin, Typen zu finden, die mitHandlerName
in registrierten Prozessoren beginnen
-
AbstractNettyHandler
ProcessAsync
Die -Methode von durch Reflektion ab und ruft den Endpunkt aufActionName
ruft überMethodInfo
NettyClient-Implementierung
Klicken Sie auf NettyClient, um den Code anzuzeigen
public sealed class NettyClient(string serverHost, int serverPort) : IDisposable
{
public EndPoint ServerEndPoint { get; } = new IPEndPoint(IPAddress.Parse(serverHost), serverPort);
private static readonly Bootstrap bootstrap = new();
private static readonly IEventLoopGroup eventLoopGroup = new SingleThreadEventLoop();
private bool _disposed;
private IChannel? _channel;
public bool IsConnected => _channel != null && _channel.Open;
public bool IsWritable => _channel != null && _channel.IsWritable;
static NettyClient()
{
bootstrap
.Group(eventLoopGroup)
.Channel<TcpSocketChannel>()
.Option(ChannelOption.SoReuseaddr, true)
.Option(ChannelOption.SoReuseport, true)
.Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
//pipeline.AddLast("ping", new IdleStateHandler(0, 5, 0));
pipeline.AddLast("framing-enc", new LengthFieldPrepender(4));
pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 4, 0, 4));
pipeline.AddLast("decoder", new DefaultDecoder());
pipeline.AddLast("encoder", new DefaultEncoder());
pipeline.AddLast("handler", new ClientMessageEntry());
}));
}
/// <summary> 连接服务器 </summary>
private async Task TryConnectAsync()
{
try
{
if (IsConnected) { return; }
_channel = await bootstrap.ConnectAsync(ServerEndPoint);
}
catch (Exception ex)
{
throw new Exception($"连接服务器失败 : {ServerEndPoint} {ex.Message}");
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="endpoint">终结点</param>
/// <param name="sync">是否同步等待响应</param>
/// <param name="body">正文</param>
public async Task SendAsync(string endpoint, bool sync = false, byte[]? body = null)
{
var message = NettyMessage.Create(endpoint, sync, body);
if (sync)
{
var task = ClientMessageSynchronizer.TryAdd(message);
try
{
await SendAsync(message);
await task;
}
catch
{
ClientMessageSynchronizer.TryRemove(message);
throw;
}
}
else
{
await SendAsync(message);
}
}
/// <summary>
/// 发送消息
/// </summary>
private async Task SendAsync(NettyMessage message)
{
await TryConnectAsync();
await _channel!.WriteAndFlushAsync(message);
}
/// <summary> 释放连接(程序员手动释放, 一般在代码使用using语句,或在finally里面Dispose) </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary> 释放连接 </summary>
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
//释放托管资源,比如嵌套的对象
if (disposing)
{
}
//释放非托管资源
if (_channel != null)
{
_channel.CloseAsync();
_channel = null;
}
_disposed = true;
}
~NettyClient()
{
Dispose(true);
}
}
-
NettyClient
Kapselt die Netty-Client-Logik und stellt Methoden zum Senden asynchroner Anforderungen (Standard) und Veröffentlichen synchroner Anforderungen bereit
-
DotNetty
stellt standardmäßig keine synchronen Anfragen bereit, aber in einigen Fällen müssen wir synchron auf die Antwort des Servers warten. Alles muss von uns selbst implementiert werden. Die Implementierung ist auch sehr einfach. Cachen Sie die Nachrichten-ID und aktivieren Sie sie anschließend Empfangen der Serverantwort. Die spezifische Implementierung lautet wie folgt: NachrichtensynchronisiererClientMessageSynchronizer
, ich werde es nicht noch einmal posten
Zusammenfassen
Bisher haben wir das auf DotNetty
basierende Kommunikationsmodul implementiert, die Kodierung und Dekodierung des Clients und des Servers implementiert, die Prozessorauswahl durchgeführt und der Client hat Synchronisierungsnachrichten usw. implementiert . Sie können es sehen in < Testen Sie im Konsolenprojekt am Ende von /span> ModusConsoleHost
synchrone und asynchrone Nachrichten und implementieren Sie einen einfachen Echo
Code-Repository
Nennen wir das Projekt vorerst
OpenDeploy
Herzlich willkommen bei der Herstellung von Ziegeln, Star
Nächster Schritt
Planen Sie den nächsten Schritt, um die Konfiguration und Erkennung von Schnittstellenprojekten basierend aufWPF
s Client zu realisieren
Artikel nachgedruckt von:Broadm
Ursprünglicher Link:https://www.cnblogs.com/broadm/p/17875559.html