当前位置:网站首页>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
边栏推荐
- 二叉树相关OJ题
- [729. My schedule I]
- Get ready for the pre-season card game MotoGP ignition champions!
- How to use FRP intranet penetration +teamviewer to quickly connect to the intranet host at home when mobile office
- Deep dive kotlin synergy (XXI): flow life cycle function
- Enterprise backup software Veritas NetBackup (NBU) 8.1.1 installation and deployment of server
- [deep learning] [original] let yolov6-0.1.0 support the txt reading dataset mode of yolov5
- 挖财股票开户安全吗?怎么开股票账户是安全?
- Accès aux données - intégration du cadre d'entité
- Cs231n notes (bottom) - applicable to 0 Foundation
猜你喜欢
[deep learning] how does deep learning affect operations research?
Clear restore the scene 31 years ago, volcanic engine ultra clear repair beyond classic concert
Summary of methods for finding intersection of ordered linked list sets
DenseNet
Summary of PHP pseudo protocol of cisp-pte
普洛斯数据中心发布DC Brain系统,科技赋能智慧化运营管理
解决CMakeList find_package找不到Qt5,找不到ECM
OneForAll安装使用
Basic introduction to the control of the row component displaying its children in the horizontal array (tutorial includes source code)
How to install MySQL
随机推荐
SQL injection of cisp-pte (Application of secondary injection)
搜索 正排索引 和 倒排索引 区别
You should have your own persistence
Cartoon: what is distributed transaction?
清晰还原31年前现场,火山引擎超清修复Beyond经典演唱会
Games101 notes (III)
[es6] add if judgment or ternary operator judgment in the template string
极坐标扇图使用场景与功能详解
Learnopongl notes (I)
[729. My Schedule i]
[deep learning] how does deep learning affect operations research?
Data verification before and after JSON to map -- custom UDF
Cartoon: what is service fusing?
Games101 notes (II)
PHP 严格模式
[deep learning] [original] let yolov6-0.1.0 support the txt reading dataset mode of yolov5
Single merchant v4.4 has the same original intention and strength!
[echart] resize lodash to realize chart adaptation when window is zoomed
HiEngine:可媲美本地的云原生内存数据库引擎
面对新的挑战,成为更好的自己--进击的技术er