当前位置:网站首页>C# TCP如何设置心跳数据包,才显得优雅呢?
C# TCP如何设置心跳数据包,才显得优雅呢?
2022-07-05 16:19:00 【若汝棋茗】
一、说明
为什么要设置心跳?
心跳机制是定时发送一个自定义的 结构体 (心跳包),让对方知道自己还活着,以确保连接的有效性的机制。 网络中的接收和发送数据都是使用操作系统中的 SOCKET 进行实现。 但是如果此 套接字 已经断开,那发送数据和接收数据的时候就一定会有问题。 可是如何判断这个套接字是否还可以使用呢? 这个就需要在系统中创建心跳机制。 其实TCP中已经为我们实现了一个叫做心跳的机制。
但是该机制受限于操作系统,而且很容易误报。所以很少被大家使用。
大家使用最多的,就是自己设计数据包,然后预留心跳格式,当对方收到心跳包时,直接返回响应包即可。
那么,按这个思路,让我们使用RRQMSocket优雅的实现吧。
二、程序集源码
2.1 源码位置
2.2 说明文档
三、安装
Nuget安装RRQMSocket
即可,具体步骤详看链接博客。
四、数据格式
4.1 设计数据格式
使用心跳之前,必须要明确数据格式,绝对不能混淆业务数据。一般在适配Plc等现成模块时,他们是有固定的数据格式,这时候你可以参阅数据处理适配器,快速的解析数据。
但是在本文中,并没有规定的格式,所以我们需要先设计一种简单高效的数据格式。
如下:
数据长度 | 数据类型 | 载荷数据 |
---|---|---|
2字节(Ushort) | 1字节(Byte) | n字节(<65535) |
4.2 解析数据格式
class MyFixedHeaderDataHandlingAdapter : CustomFixedHeaderDataHandlingAdapter<MyRequestInfo>
{
public override int HeaderLength => 3;
protected override MyRequestInfo GetInstance()
{
return new MyRequestInfo();
}
}
class MyRequestInfo : IFixedHeaderRequestInfo
{
public DataType DataType {
get; set; }
public byte[] Data {
get; set; }
public int BodyLength {
get; private set; }
public bool OnParsingBody(byte[] body)
{
if (body.Length == this.BodyLength)
{
this.Data = body;
return true;
}
return false;
}
public bool OnParsingHeader(byte[] header)
{
if (header.Length == 3)
{
this.BodyLength = RRQMBitConverter.Default.ToUInt16(header, 0)-1;
this.DataType = (DataType)header[2];
return true;
}
return false;
}
public void Package(ByteBlock byteBlock)
{
byteBlock.Write((ushort)((this.Data == null ? 0 : this.Data.Length) + 1));
byteBlock.Write((byte)this.DataType);
byteBlock.Write(Data);
}
public byte[] PackageAsBytes()
{
using ByteBlock byteBlock = new ByteBlock();
this.Package(byteBlock);
return byteBlock.ToArray();
}
public override string ToString()
{
return $"数据类型={
this.DataType},数据={
(this.Data==null?"null":Encoding.UTF8.GetString(this.Data))}";
}
}
enum DataType : byte
{
Ping,
Pong,
Data
}
五、创建扩展类
/// <summary>
/// 一个心跳计数器扩展。
/// </summary>
static class DependencyExtensions
{
public static readonly DependencyProperty HeartbeatTimerProperty =
DependencyProperty.Register("HeartbeatTimer", typeof(Timer), typeof(DependencyExtensions), null);
public static bool Ping<TClient>(this TClient client)where TClient:ITcpClientBase
{
try
{
client.Send(new MyRequestInfo() {
DataType = DataType.Ping }.PackageAsBytes());
return true;
}
catch(Exception ex)
{
client.Logger.Exception(ex);
}
return false;
}
public static bool Pong<TClient>(this TClient client) where TClient : ITcpClientBase
{
try
{
client.Send(new MyRequestInfo() {
DataType = DataType.Pong }.PackageAsBytes());
return true;
}
catch (Exception ex)
{
client.Logger.Exception(ex);
}
return false;
}
}
六、创建心跳插件类
class HeartbeatAndReceivePlugin : TcpPluginBase
{
private readonly int m_timeTick;
[DependencyInject(1000*5)]
public HeartbeatAndReceivePlugin(int timeTick)
{
this.m_timeTick = timeTick;
}
protected override void OnConnecting(ITcpClientBase client, ClientOperationEventArgs e)
{
client.SetDataHandlingAdapter(new MyFixedHeaderDataHandlingAdapter());//设置适配器。
base.OnConnecting(client, e);
}
protected override void OnConnected(ITcpClientBase client, RRQMEventArgs e)
{
if (client is ISocketClient)
{
return;//此处可判断,如果为服务器,则不用使用心跳。
}
if (client.GetValue<Timer>(DependencyExtensions.HeartbeatTimerProperty) is Timer timer)
{
timer.Dispose();
}
client.SetValue(DependencyExtensions.HeartbeatTimerProperty,new Timer((o)=>
{
client.Ping();
},null,0, m_timeTick));
base.OnConnected(client, e);
}
protected override void OnDisconnected(ITcpClientBase client, ClientDisconnectedEventArgs e)
{
base.OnDisconnected(client, e);
if (client.GetValue<Timer>(DependencyExtensions.HeartbeatTimerProperty) is Timer timer)
{
timer.Dispose();
client.SetValue(DependencyExtensions.HeartbeatTimerProperty,null);
}
}
protected override void OnReceivedData(ITcpClientBase client, ReceivedDataEventArgs e)
{
if (e.RequestInfo is MyRequestInfo myRequest)
{
client.Logger.Message(myRequest.ToString());
if (myRequest.DataType== DataType.Ping)
{
client.Pong();
}
}
base.OnReceivedData(client, e);
}
}
七、测试、启动
class Program
{
static TcpService service = new TcpService();
static TcpClient tcpClient = new TcpClient();
static void Main(string[] args)
{
ConsoleAction consoleAction = new ConsoleAction();
service.Setup(new RRQMConfig()//载入配置
.SetListenIPHosts(new IPHost[] {
new IPHost("127.0.0.1:7789"), new IPHost(7790) })//同时监听两个地址
.SetMaxCount(10000)
.UsePlugin()
.SetThreadCount(10))
.Start();//启动
service.AddPlugin<HeartbeatAndReceivePlugin>();
service.Logger.Message("服务器成功启动");
tcpClient.Setup(new RRQMConfig()
.SetRemoteIPHost(new IPHost("127.0.0.1:7789"))
.UsePlugin()
.SetBufferLength(1024 * 10));
tcpClient.AddPlugin<HeartbeatAndReceivePlugin>();
tcpClient.Connect();
tcpClient.Logger.Message("客户端成功连接");
consoleAction.OnException += ConsoleAction_OnException;
consoleAction.Add("1", "发送心跳", () =>
{
tcpClient.Ping();
});
consoleAction.Add("2", "发送数据", () =>
{
tcpClient.Send(new MyRequestInfo()
{
DataType = DataType.Data,
Data = Encoding.UTF8.GetBytes(Console.ReadLine())
}
.PackageAsBytes());
});
consoleAction.ShowAll();
while (true)
{
consoleAction.Run(Console.ReadLine());
}
}
private static void ConsoleAction_OnException(Exception obj)
{
Console.WriteLine(obj);
}
}
八、效果
本文示例demo
边栏推荐
- Basic introduction to the control of the row component displaying its children in the horizontal array (tutorial includes source code)
- Migrate /home partition
- Apple has abandoned navigationview and used navigationstack and navigationsplitview to implement swiftui navigation
- Can you help me see what the problem is? [ERROR] Could not execute SQL stateme
- [brush questions] effective Sudoku
- Data access - entityframework integration
- 阿掌的怀念
- 普洛斯数据中心发布DC Brain系统,科技赋能智慧化运营管理
- Summary of methods for finding intersection of ordered linked list sets
- 国产芯片产业链两条路齐头并进,ASML真慌了而大举加大合作力度
猜你喜欢
有序链表集合求交集 方法 总结
Hiengine: comparable to the local cloud native memory database engine
[team PK competition] the task of this week has been opened | question answering challenge to consolidate the knowledge of commodity details
中间表是如何被消灭的?
解决CMakeList find_package找不到Qt5,找不到ECM
Enter a command with the keyboard
How does win11 change icons for applications? Win11 method of changing icons for applications
普洛斯数据中心发布DC Brain系统,科技赋能智慧化运营管理
2020-2022两周年创作纪念日
Detailed explanation of use scenarios and functions of polar coordinate sector diagram
随机推荐
Explain in detail the functions and underlying implementation logic of the groups sets statement in SQL
Do sqlserver have any requirements for database performance when doing CDC
树莓派4b安装Pytorch1.11
Pspnet | semantic segmentation and scene analysis
机器学习编译第2讲:张量程序抽象
Data access - entityframework integration
Single merchant v4.4 has the same original intention and strength!
Summary of PHP pseudo protocol of cisp-pte
Starkware: to build ZK "universe"
Deep learning plus
Apple has abandoned navigationview and used navigationstack and navigationsplitview to implement swiftui navigation
Desci: is decentralized science the new trend of Web3.0?
StarkWare:欲构建ZK“宇宙”
Cartoon: what is service fusing?
Some cognitive thinking
[deep learning] [original] let yolov6-0.1.0 support the txt reading dataset mode of yolov5
[brush title] goose factory shirt problem
Hiengine: comparable to the local cloud native memory database engine
[brush questions] effective Sudoku
sqlserver 做cdc 要对数据库性能有什么要求么