Implement an interface automatic publishing tool based on DotNetty - communication implementation

Communication based on DotNetty

DotNetty : It is a version of Netty implemented by Microsoft's Azure team using C#. It is an excellent network library for the .NET platform.

Project Introduction

OpenDeploy.Communication Class library project is a communication-related infrastructure layer

image

  • Codec Module implements encoding and decoding

  • Convention Module definition conventions, such as abstract business Handler, message carrier NettyMessage, message context 'NettyContext', etc.

Custom message format

The message class is NettyMessage , which encapsulates the message header NettyHeader and the message body Body

image

NettyMessage

encapsulates the message header NettyHeader and message body Body

NettyMessage click to view code

/// <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

Message header, including the unique identifier of the request, whether to synchronize the message, endpoint, etc., will be serialized into JSON when transmitting data.

NettyHeader click to view code

/// <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();
}


  • The unique identifier of the request message RequestId is used to uniquely identify the message. It is mainly used to send synchronous requests, because the default message is asynchronous, just send it out without waiting for a response a>

  • Whether to synchronize messages Sync , it is not necessary, mainly for visualization and easy debugging

  • Endpoint EndPoint , (drawing lessons from MVC, agreed to be Control/Action mode), the server directly parses out the corresponding processor

Encoder

DefaultEncoder click to view code

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

DefaultDecoder click to view code

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 implementation

NettyServer click to view code

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退出了...");
        }

    }
}

  • Finally we added ServerMessageEntry to the server pipeline as the entry point for message processing

ServerMessageEntry click to view code

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}");
        }
    }
}

  • According to the convention, the class that inherits AbstractNettyHandler is regarded as a business processor

  • ServerMessageEntry After getting the message, first encapsulate the message as NettyContext, similar to HttpContext in MVC, which encapsulates the request and response objects, and internally parses the request EndPoint, split into HandlerNameActionName

  • DefaultNettyHandlerSelector Provides methods for registering processors RegisterHandlerTypes, and methods for selecting processors SelectHandler

  • SelectHandler, the default rule is to find types starting with HandlerName in registered processors

  • AbstractNettyHandler 's ProcessAsync method, through ActionName, gets MethodInfo through reflection, and calls the endpoint

NettyClient implementation

NettyClient click to view code

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 Encapsulates Netty client logic and provides methods for sending asynchronous requests (default) and publishing synchronous requests

  • DotNetty does not provide synchronous requests by default, but in some cases we need to wait for the server's response synchronously. All needs to be implemented by ourselves. The implementation is also very simple. Cache the message ID and activate it after receiving the server response. The specific implementation is as follows Message synchronizer ClientMessageSynchronizer, I won’t post it again

Summarize

So far, we have implemented the communication module based on DotNetty , implemented the encoding and decoding of the client and the server, processor selection, and the client has implemented synchronization messages, etc. You can see it in < In the console project at the end of /span>  modeConsoleHost , test synchronous and asynchronous messages and implement a simple Echo

code repository

Let’s call the project OpenDeploy for now

Welcome everyone to make bricks, Star

Next step

Plan the next step to realize the configuration and discovery of interface projects based onWPF’s client

Article reprinted from:Broadm

Original link:https://www.cnblogs.com/broadm/p/17875559.html

Guess you like

Origin blog.csdn.net/sdgfafg_25/article/details/134799249