【网络通讯】【DotNetty实现(四)】

网络通讯系列

第五章 【网络通讯】【详细场景下的Demo示例(五)】
第四章 【网络通讯】【DotNetty实现(四)】
第三章 【网络通讯】【SuperSocket实现(三)】
第二章 【网络通讯】【Socket实现(二)】
第一章 【网络通讯】【通讯协议(一)】



前言

介绍使用DotNetty实现网络通讯。


一、程序设计

使用第三方库DotNetty来实现上位机客户端。
官网:https://github.com/Azure/DotNetty/
可下载源码(如果无法登上github下载,可在评论区留言,有需要我再上传源码资料)

关键字:高性能、易用性

DotNetty的整体架构可以分为以下四个部分:

Channel:通道是业务逻辑和网络逻辑之间的桥梁。在DotNetty中,所有的网络数据都通过Channel来进行传输。

EventLoop:事件循环是一个单独的线程,用来处理特定类型的事件。每个EventLoop都会绑定一个Selector,用于监听Channel中感兴趣的事件。当事件发生时,该EventLoop会被唤醒来处理该事件。

ChannelPipeline:通道管道是一系列的处理器链,用于处理输入和输出的数据流。在DotNetty中,所有的数据都经过这个管道,在这个管道上可以添加多个处理器来实现业务逻辑。

ChannelHandlerContext:通道处理器上下文包含了当前通道的所有状态信息,每个ChannelHandlerContext都与一个EventLoop相关联。在处理业务逻辑时,可以通过ChannelHandlerContext来发送数据、获取当前通道的状态等。

在DotNetty中,还有许多组件模块,其中比较重要的有:

Transport:传输层模块,用于处理不同协议的网络连接。

Codec:编解码模块,用于处理消息的编码和解码。

Handler:处理器模块,用于实现具体的业务逻辑。

Bootstrap:启动器模块,用于配置和启动应用程序。

怎么选择架构和组件呢?需要回顾第一章,了解我们的需求是什么?
接收的是16进制字节、前面2个字节是接收内容的长度,协议规定一条指令最大1Kb即1024字节,我们可以通道Bootstrap来配置这个解析的规则,然后通过通道ChannelHandlerContext与EventLoop相关联实现接收数据,通过Channel实现数据发送。

在这里我们用到的是
要实现客户端,我们需要设计三个接口、一个事件、两个类
接口分别是连接服务器、断连服务器、发送服务器内容;
事件是接收服务器内容事件;
实现类分为两个,
一个是用于接收内容的通道类;
一个是实现接口和事件内容的客户端类,包含解析规则。

后续优化,在客户端中实现对连接多个服务器需要有多个客户端的管理、解决连接不存在IP的服务器时时间过长问题(Ping功能)等等,这个会在第五章中的Demo实例中有完整解决方案资源可下载。

二、代码

需要添加的NuGet程序包是:

<PackageReference Include="DotNetty.Handlers" Version="0.7.6" />

通道处理类

/// <summary>
/// Tcp通道处理
/// </summary>
internal class TcpChannelHandler : SimpleChannelInboundHandler<object>
{
    private TcpSocketClient tcpSocketClient;

    /// <summary>
    /// Tcp
    /// </summary>
    /// <param name="client">client</param>
    public TcpChannelHandler(TcpSocketClient client) 
    {
        tcpSocketClient = client;
    }

    /// <summary>
    /// 通道连接后调用
    /// </summary>
    /// <param name="context">上下文</param>
    public override void ChannelActive(IChannelHandlerContext context)
    {
        tcpSocketClient.Client = context.Channel.Id.AsShortText();
        tcpSocketClient.Connected = true;
    }

    /// <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)
    {
        tcpSocketClient.HandlerException(exception);
    }

    /// <summary>
    /// 连接断开
    /// </summary>
    /// <param name="context">上下文</param>
    public override void ChannelInactive(IChannelHandlerContext context)
    {
        tcpSocketClient.Close();
        tcpSocketClient.Connected = false;
        tcpSocketClient.HandleDiconnect();
    }

    /// <summary>
    /// 读取通道数据
    /// </summary>
    /// <param name="ctx">通道上下文</param>
    /// <param name="msg">数据缓冲区</param>
    protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
    {
        if (msg is not IByteBuffer buffer)
        {
            return;
        }

        int readableBytes = buffer.ReadableBytes;
        if (readableBytes == 0)
        {
            return;
        }

        var bytes = new byte[readableBytes - 1];
        buffer.GetBytes(buffer.ReaderIndex, bytes);
        tcpSocketClient.ReceivePacket(bytes);
    }
}

客户端类

/// <summary>
/// Client
/// </summary>
internal class TcpSocketClient
{
    private MultithreadEventLoopGroup? _group;
    private Bootstrap? _bootstrap;
    private IChannel? _workChannel;

    /// <summary>
    /// 接收命令事件
    /// </summary>
    public event Action<byte[]>? OnReceiveRawPacketComomand;

    /// <summary>
    /// Client
    /// </summary>
    public string? Client { get; set; }

    /// <summary>
    /// 已连接
    /// </summary>
    public bool Connected { get; set; }

    /// <summary>
    /// 接收信息包
    /// </summary>
    /// <param name="bytes">数据</param>
    public void ReceivePacket(byte[] bytes)
    {
        OnReceiveRawPacketComomand?.Invoke(bytes);
    }

    /// <summary>
    /// 断开连接
    /// </summary>
    public void HandleDiconnect()
    {
    }

    /// <summary>
    /// 异常处理
    /// </summary>
    /// <param name="ex">异常</param>
    public void HandlerException(Exception ex)
    {
    }

    /// <summary>
    /// Connect
    /// </summary>
    /// <returns>true:成功;false:失败</returns>
    public bool Connect(string hostAddress, int hostPort)
    {
       if (_group == null)
	   {
			_group = new MultithreadEventLoopGroup();
	   }

       if (_bootstrap == null)
       {
       		_bootstrap = new Bootstrap();
	      	_bootstrap
          		.Group(_group)
	           	.Channel<TcpSocketChannel>()
               	.Option(ChannelOption.TcpNodelay, true)
               	.Option(ChannelOption.ConnectTimeout, TimeSpan.FromSeconds(3))
              	.Handler(new ActionChannelInitializer<ISocketChannel>(ch =>
              	{
	           		IChannelPipeline pipeline = ch.Pipeline;
					
					// 发送时加上2个字节的头
                 	pipeline.AddLast("framing-enc", new LengthFieldPrepender(ByteOrder.LittleEndian, 2, 0, false));

                	// 解包时移除2个字节的头,要解码的最大数据包长度1024、长度域的偏移量0、长度域所占用的字节数2,长度字段值需要调整的值0,解码器在返回帧之前应该跳过的字节数2
                	pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ByteOrder.LittleEndian, 1024, 0, 2, 0, 2, false));
                 
                 	pipeline.AddLast(new TcpChannelHandler(this));
                    }));
            }

            Close();
            _workChannel = _bootstrap.ConnectAsync(IPAddress.Parse(hostAddress), hostPort).Result;
            return true;
		}
        return false;
    }

    /// <summary>
    /// 断开连接
    /// </summary>
    public void Disconnect()
    {
        if (_workChannel != null && _workChannel.Active)
        {
        	_workChannel.DisconnectAsync().Wait();
          	Connected = false;
          	_workChannel = null;
       	}
    }

    /// <summary>
    /// 发送数据包
    /// </summary>
    /// <param name="bufer">数据</param>
    public void Send(byte[] bufer)
    {
        if (_workChannel != null && _workChannel.Open)
       	{
    		IByteBuffer byteBuffer = Unpooled.Buffer(bufer.Length + 2);
          	byteBuffer.WriteByte(0x04);
          	byteBuffer.WriteBytes(bufer);
          	byteBuffer.WriteByte(0x00);
         	_workChannel.WriteAndFlushAsync(byteBuffer);
      	}
    }
}
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值