当前位置:网站首页>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
边栏推荐
- Mongodb getting started Tutorial Part 04 mongodb client
- Global Data Center released DC brain system, enabling intelligent operation and management through science and technology
- Seaborn draws 11 histograms
- 2020-2022 two-year anniversary of creation
- How was the middle table destroyed?
- [深度学习][原创]让yolov6-0.1.0支持yolov5的txt读取数据集模式
- [61dctf]fm
- "21 days proficient in typescript-3" - install and build a typescript development environment md
- 【刷题篇】有效的数独
- [deep learning] how does deep learning affect operations research?
猜你喜欢

有序链表集合求交集 方法 总结

scratch五彩糖葫芦 电子学会图形化编程scratch等级考试三级真题和答案解析2022年6月

Win11 prompt: what if the software cannot be downloaded safely? Win11 cannot download software safely

Fleet tutorial 09 basic introduction to navigationrail (tutorial includes source code)

Single merchant v4.4 has the same original intention and strength!

详解SQL中Groupings Sets 语句的功能和底层实现逻辑

极坐标扇图使用场景与功能详解

单商户 V4.4,初心未变,实力依旧!

如何安装mysql

Jarvis OJ Telnet Protocol
随机推荐
Some cognitive thinking
Jarvis OJ 远程登录协议
详解SQL中Groupings Sets 语句的功能和底层实现逻辑
2020-2022两周年创作纪念日
Deep dive kotlin synergy (XXI): flow life cycle function
麻烦问下,DMS中使用Redis语法是以云数据库Redis社区版的命令为参考的嘛
Starkware: to build ZK "universe"
China Radio and television officially launched 5g services, and China Mobile quickly launched free services to retain users
帮忙看看是什么问题可以吗?[ERROR] Could not execute SQL stateme
Practice independent and controllable 3.0 and truly create the open source business of the Chinese people
详解SQL中Groupings Sets 语句的功能和底层实现逻辑
Desci: is decentralized science the new trend of Web3.0?
数据访问 - EntityFramework集成
Learnopongl notes (I)
Explain in detail the functions and underlying implementation logic of the groups sets statement in SQL
2020-2022 two-year anniversary of creation
Using graylog alarm function to realize the regular work reminder of nail group robots
WSL2.0安装
File operation --i/o
How to uninstall MySQL cleanly