网络通讯系列
第五章 【网络通讯】【详细场景下的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);
}
}
}