【网络通讯】【SuperSocket实现(三)】

网络通讯系列

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



前言

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


一、程序设计

使用第三方库SuperSocket来实现上位机客户端。
官网:https://www.supersocket.net/
主要介绍如图:
在这里插入图片描述
关键字:高性能、简单易用

SuperSocket内置的常用协议实现模版有以下几个:

  • TerminatorReceiveFilter (SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase)
    结束符协议
  • CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)
    固定数量分隔符协议
  • FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)
    固定请求大小的协议
  • BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)
    带起止符的协议
  • FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)
    头部格式固定并且包含内容长度的协议

怎么选择哪一个协议?需要回顾第一章,了解我们的需求是什么?
接收的是16进制字节、前面2个字节是接收内容的长度,因此我们使用头部格式固定并且包含内容长度的协议

上位机作为服务器,因此我们使用SuperSocket.ClientEngine.EasyClient客户端对象

在这里我们用到的是
要实现客户端,我们需要设计三个接口、三个事件、三个类
接口分别是连接服务器、断连服务器、发送服务器内容;
事件分别是接收服务器内容事件、连接成功事件、断开连接事件;
实现类分为三个,
一个是使用头部格式固定并且包含内容长度的协议的协议解析收到内容的解析类;
一个是解析需要用到的原始数据包信息实体类;
一个是实现接口和事件内容的功能类。

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

二、代码

需要添加的NuGet程序包是:

<PackageReference Include="SuperSocket.ClientEngine" Version="0.10.0" />
<PackageReference Include="SuperSocket.ProtoBase" Version="1.7.0.17" />

实体类:

 /// <summary>
 /// 原始数据包信息
 /// </summary>
 public class PackageInfo : SuperSocket.ProtoBase.IPackageInfo
 {
     /// <summary>
     /// 数据
     /// </summary>
     public byte[] Buffers { get; }

     /// <summary>
     /// 长度
     /// </summary>
     public int Length { get; }

     /// <summary>
     /// 客户端
     /// </summary>
     public SuperSocket.ClientEngine.EasyClient Client { get; }

     /// <summary>
     /// 构造函数
     /// </summary>
     /// <param name="length">长度</param>
     /// <param name="client">客户端</param>
     public PackageInfo(int length, SuperSocket.ClientEngine.EasyClient client)
     {
         Length = length;
         Buffers = new byte[length];
         Client = client;
     }
 }

解析类:

    /// <summary>
    /// 长度过滤器
    /// </summary>
    public class LengthReceiveFilter : SuperSocket.ProtoBase.FixedHeaderReceiveFilter<PackageInfo>
    {
        private SuperSocket.ClientEngine.EasyClient _client;

        /// <summary>
        /// 构造
        /// </summary>
        /// <param name="client">客户端</param>
        public LengthReceiveFilter(SuperSocket.ClientEngine.EasyClient client) : base(2)  //头部格式固定2个字节(内容长度)
        {
            _client = client;
        }

        /// <summary>
        /// 解包
        /// </summary>
        /// <param name="bufferStream">流</param>
        /// <returns>原始包</returns>
        public override PackageInfo ResolvePackage(SuperSocket.ProtoBase.IBufferStream bufferStream)
        {
            try
            {
                SuperSocket.ProtoBase.BufferStream buffer = (SuperSocket.ProtoBase.BufferStream)bufferStream;
                buffer.ReadInt16(); //移除长度2个字节(头部格式固定内容长度)
                int len = (int)(buffer.Length - buffer.Position);
                var pkg = new PackageInfo(len, _client);
                buffer.Read(pkg.Buffers, 0, len);
                buffer.Close();
                return pkg;
            }
            catch
            {
                // 可能因断点没有接到信息导致SuperSocketClient缓存区满会出现异常
                var pkg = new PackageInfo(0, _client);
                return pkg;
            }
        }

        /// <summary>
        /// 获取长度
        /// </summary>
        /// <param name="bufferStream">流</param>
        /// <param name="length">位置</param>
        /// <returns>长度</returns>
        protected override int GetBodyLengthFromHeader(SuperSocket.ProtoBase.IBufferStream bufferStream, int length)
        {
            SuperSocket.ProtoBase.BufferStream buffer = (SuperSocket.ProtoBase.BufferStream)bufferStream;
            int len = buffer.ReadInt16(true);   //长度是小端序,头部格式固定2个字节(内容长度)
            return len;
        }
    }

功能类:

 public class SuperSocketClient
 {
     private ConcurrentDictionary<string, EasyClient>? _allClients = null;
     public event Action<int, byte[], int>? OnReceiveData;
     public event Action<EasyClient>? OnClientDiconnect;
     public event Action<EasyClient?>? OnClientConnect;

     public SuperSocketClient()
     {
         if (_allClients == null)
         {
             _allClients = new ConcurrentDictionary<string, EasyClient>();
         }
     }

     public int Connect(string hostAddress, int hostPort)
     {
         EasyClient? client = new EasyClient();
         if (_allClients?.TryGetValue(hostAddress+ hostPort, out client) == false)  //不存在
         {
             client = new EasyClient();

             //注册接收信息事件
             client.Initialize(new LengthReceiveFilter(client), (req) =>
             {
                 if (req.Client.Socket == null)
                 {
                     return;
                 }
                 OnReceiveData?.Invoke(client.Socket.Handle.ToInt32(), req.Buffers, req.Length);
             });
             //注册连接事件
             client.Connected += (sender, e) =>
             {
                 OnClientConnect?.Invoke((sender as EasyClient) ?? null);
             };
             //注册错误事件
             client.Error += (sender, e) =>
             {
                 if (sender is EasyClient client && client is not null)
                 {
                     Disconnect(client);
                 }
             };
             _allClients.TryAdd(hostAddress + hostPort, client);
         }

         if (client?.IsConnected == true)
         {
             return client.Socket.Handle.ToInt32();
         }
         if (client?.IsConnected == false)
         {
             //在.NetFramework4.8和.Net6.0均没有任何问题,但是在.Net8.0上有BUG,会导致在VS2022调试运行时阻塞在这里(中概率偶发),解决办法是取消勾选调试配置中的启动热重载
             lock (client)
             {
                 //bool connected = client.ConnectAsync(new IPEndPoint(IPAddress.Parse(ip), port)).Result; //这种用法在.Net8.0上不断地进行重复连接、断开操作会导致网络IO阻塞
                 client.ConnectAsync(new IPEndPoint(IPAddress.Parse(hostAddress), hostPort)).Wait(TimeSpan.FromSeconds(3));
                 if (client.IsConnected)
                 {
                     return client.Socket.Handle.ToInt32();
                 }
             }
         }
         return 0;

     }

     public int Disconnect(EasyClient? client)
     {
         if (client == null)
         {
             return 0;
         }
         string? key = _allClients?.FirstOrDefault(o => o.Value.Socket.Handle.ToInt32() == client.Socket.Handle.ToInt32()).Key;
         if (!string.IsNullOrEmpty(key) && _allClients?.TryGetValue(key, out client) == true)
         {
             bool flag = client.IsConnected;
             lock (client)
             {
                 if (client.IsConnected)
                 {
                     client.Close().Wait(TimeSpan.FromSeconds(5));
                 }
             }
             if (flag && !client.IsConnected) //由连接状态变成非连接状态才执行
             {
                 OnClientDiconnect?.Invoke(client);
             }
             return client.Socket.Handle.ToInt32();
         }
         return 0;
     }

     public int Send(EasyClient? client, byte[] sendDataUnit, int length)
     {
         if (client == null)
         {
             return 0;
         }
         string? key = _allClients?.FirstOrDefault(o => o.Value.Socket.Handle.ToInt32() == client.Socket.Handle.ToInt32()).Key;
         if (!string.IsNullOrEmpty(key) && _allClients?.TryGetValue(key, out client) == true)
         {
             lock (client)
             {
                 if (client.IsConnected == false)
                 {
                     return 0;
                 }
                 client.Send(sendDataUnit);
             }
             return 1;
         }
         return 0;
     }
 }
Supersocket是基于.NET平台的开源框架,它提供了多种网络通信实现方式,比如TCP、UDP等协议,可以轻松实现高效稳定的网络通信。而Mudbus是一种常用的工业现场总线协议,用于实现工控领域的数据采集、传输和控制。 Supersocket实现Mudbus的过程主要是通过定义自定义协议解析器,将Mudbus协议包解析成对应的控制命令,然后将命令通过TCP或UDP等协议发送到目标设备,最后将设备返回的数据解析成Mudbus协议包并返回给请求方。具体实现方式如下: 1. 定义自定义协议解析器,重写Supersocket框架中的接口方法,包括OnReceive、OnSessionClosed、OnSessionStarted等方法,实现对Mudbus协议包的解析和命令的发送。 2. 在解析器中实现Mudbus协议的解析,包括使用二进制转换将原始数据转换成Mudbus协议中定义的数据格式。同时,解析器还需实现对应的控制命令的生成和发送,以及对设备返回数据的处理和解析。 3. 通过Supersocket框架提供的TcpServer、TcpClient等类实现网络通信功能,将已经解析好的Mudbus控制命令通过TCP或UDP等协议发送到目标设备,将设备返回的数据解析成Mudbus协议包并返回给请求方。 Supersocket实现Mudbus协议的过程相对复杂,需要对网络通信和协议解析等方面有一定的技术要求,但可以提高工控领域的数据采集、传输和控制的效率和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值