Implementieren Sie ein automatisches Schnittstellen-Publishing-Tool basierend auf DotNetty – Kommunikationsimplementierung

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

Bild

  • Codec Das Modul implementiert die Kodierung und Dekodierung

  • Convention Moduldefinitionskonventionen, wie abstrakter Geschäftshandler, Nachrichtenträger NettyMessage, Nachrichtenkontext „NettyContext“ usw.

Benutzerdefiniertes Nachrichtenformat

Die Nachrichtenklasse ist NettyMessage , die den Nachrichtenkopf NettyHeader und den Nachrichtentext  kapseltBody

Bild

NettyMessage

kapselt den Nachrichtenkopf NettyHeader und den Nachrichtentext Body

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 als NettyContext, ähnlich wie HttpContext in MVC, das die Anforderungs- und Antwortobjekte kapselt und die Anforderung intern analysiert EndPoint, aufgeteilt in HandlerNameActionName

  • DefaultNettyHandlerSelector Bietet Methoden zum Registrieren von Prozessoren RegisterHandlerTypes und Methoden zum Auswählen von Prozessoren SelectHandler

  • SelectHandler, die Standardregel besteht darin, Typen zu finden, die mit HandlerName in registrierten Prozessoren beginnen

  • AbstractNettyHandlerProcessAsync Die -Methode von  durch Reflektion ab und ruft den Endpunkt aufActionName  ruft über MethodInfo

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: Nachrichtensynchronisierer ClientMessageSynchronizer, 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 aufWPFs Client zu realisieren

Artikel nachgedruckt von:Broadm

Ursprünglicher Link:https://www.cnblogs.com/broadm/p/17875559.html

Supongo que te gusta

Origin blog.csdn.net/sdgfafg_25/article/details/134799249
Recomendado
Clasificación