在开发客户端程序、设备上位机软件、物联网系统时,TCP 长连接是一种非常常见的通信方式。
例如:
上位机与工业设备之间保持连接;
客户端与服务器之间实时通信;
智能硬件设备持续上传状态数据。
但是 TCP 本身只负责保证数据可靠传输,并不会主动告诉我们当前连接是否还有效。
例如:
网络突然断开;
设备异常掉电;
路由器连接中断;
对端程序崩溃;
这些情况下,TCP连接可能不会立即触发断开事件,导致应用层仍然认为连接正常。
因此,在实际项目中通常需要增加心跳检测机制(Heartbeat),通过定时发送数据包检测双方连接状态。
本文将介绍如何使用 C# 实现一个简单的 TCP 心跳检测机制,适用于客户端、服务器、设备通信等场景。
一、为什么TCP需要心跳检测
很多开发者刚接触 TCP 时,会认为:
“TCP连接成功后,只要没有异常,就一直保持连接。”
实际情况并不是这样。
TCP连接状态主要依赖操作系统维护,但是操作系统并不能实时知道网络另一端是否正常。
例如:
客户端连接设备:
Client <--------------> Device
TCP Connected
此时设备突然断电:
Client <--------------> Device(关闭)
客户端可能仍然保持:
Socket Connected = true
但是实际上已经无法通信。
如果此时发送业务数据:
发送数据
↓
等待响应
↓
超时
会导致业务逻辑阻塞。
因此通常增加心跳:
Client Device
Ping ------------>
<------------ Pong
Ping ------------>
<------------ Pong
通过固定周期通信:
判断连接是否正常;
检测设备在线状态;
及时释放异常连接;
重新建立连接。
二、设计一个简单的心跳机制
一个基础的 TCP 心跳主要包含几个部分:
心跳发送定时器
心跳消息格式
接收响应处理
超时判断
例如定义一个简单协议:
客户端发送:
{
"type":"heartbeat",
"time":"2026-08-11 10:00:00"
}
设备返回:
{
"type":"heartbeat_response"
}
实际项目中可能使用:
JSON
二进制协议
自定义数据帧
例如:
AA 55 01 00 FF
根据设备协议进行定义即可。
三、C#实现TCP客户端心跳管理
首先创建一个简单的 TCP 客户端。
public class TcpClientManager
{
private TcpClient tcpClient;
private NetworkStream networkStream;
public async Task ConnectAsync(string ip, int port)
{
tcpClient = new TcpClient();
await tcpClient.ConnectAsync(ip, port);
networkStream = tcpClient.GetStream();
Console.WriteLine("TCP连接成功");
}
}
连接成功后,可以启动心跳任务。
定义心跳间隔:
private readonly int heartbeatInterval = 5000;
private DateTime lastHeartbeatTime;
创建心跳循环:
public async Task StartHeartbeatAsync()
{
while(true)
{
await SendHeartbeatAsync();
await Task.Delay(heartbeatInterval);
}
}
这里每5秒发送一次心跳。
四、发送心跳数据
实现发送方法:
private async Task SendHeartbeatAsync()
{
if(networkStream == null)
{
return;
}
string message = "{\"type\":\"heartbeat\"}";
byte[] data = Encoding.UTF8.GetBytes(message);
await networkStream.WriteAsync(
data,
0,
data.Length
);
lastHeartbeatTime = DateTime.Now;
Console.WriteLine("发送心跳");
}
发送完成后,需要等待设备返回响应。
五、接收心跳响应与超时检测
启动接收线程:
public async Task ReceiveAsync()
{
byte[] buffer = new byte[1024];
while(true)
{
int length = await networkStream.ReadAsync(
buffer,
0,
buffer.Length
);
if(length == 0)
{
Console.WriteLine("连接断开");
break;
}
string message =
Encoding.UTF8.GetString(
buffer,
0,
length
);
HandleMessage(message);
}
}
处理收到的数据:
private DateTime lastReceiveTime;
private void HandleMessage(string message)
{
Console.WriteLine(
$"收到数据:{message}"
);
if(message.Contains("heartbeat_response"))
{
lastReceiveTime = DateTime.Now;
Console.WriteLine(
"心跳响应正常"
);
}
}
然后增加超时检测:
private bool CheckHeartbeatTimeout()
{
TimeSpan span =
DateTime.Now - lastReceiveTime;
if(span.TotalSeconds > 15)
{
return true;
}
return false;
}
例如:
5秒发送一次心跳;
15秒没有收到响应;
认为设备已经离线。
六、实际项目中的优化方向
上面的代码适合演示基本流程,实际项目中通常还需要进一步优化。
1. 心跳包不要和业务数据混合
建议定义独立消息类型:
例如:
消息类型
0x01 登录
0x02 心跳
0x03 数据上传
0x04 控制指令
收到数据后:
switch(type)
{
case Heartbeat:
HandleHeartbeat();
break;
case Data:
HandleData();
break;
}
这样后期扩展更加方便。
2. 增加自动重连机制
设备断开后:
检测断开
↓
关闭Socket
↓
等待
↓
重新连接
↓
恢复通信
例如:
while(!connected)
{
try
{
await ConnectAsync();
}
catch
{
await Task.Delay(3000);
}
}
3. 避免多个线程同时操作Socket
TCP通信中经常出现:
接收线程;
心跳线程;
业务发送线程。
多个线程同时Write可能导致数据混乱。
通常增加发送队列:
业务线程
↓
Message Queue
↓
TCP发送线程
↓
Socket
这也是很多工业上位机程序采用的设计方式。