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