没有神的光环,你孤独而平凡~

心跳机制作用:

  • 死链是指在网络通信中,由于底层网络问题导致数据无法正常传输,但连接状态仍然保持的一种异常情况。通过心跳检测和超时机制,可以有效检测和处理死链,确保连接的可靠性和资源的有效利用。
  • 保活

传输层 keepalive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

int main() {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

// 启用 keepalive 选项
int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) {
perror("setsockopt SO_KEEPALIVE");
close(fd);
exit(EXIT_FAILURE);
}

// 设置 TCP_KEEPIDLE 为 3600 秒(1 小时)
int keepidle = 3600;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)) < 0) {
perror("setsockopt TCP_KEEPIDLE");
close(fd);
exit(EXIT_FAILURE);
}

// 设置 TCP_KEEPINTVL 为 60 秒(1 分钟)
int keepintvl = 60;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)) < 0) {
perror("setsockopt TCP_KEEPINTVL");
close(fd);
exit(EXIT_FAILURE);
}

// 设置 TCP_KEEPCNT 为 5 次
int keepcnt = 5;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)) < 0) {
perror("setsockopt TCP_KEEPCNT");
close(fd);
exit(EXIT_FAILURE);
}

// 打印设置后的值
printf("Keepalive settings:\n");
printf("TCP_KEEPIDLE: %d seconds\n", keepidle);
printf("TCP_KEEPINTVL: %d seconds\n", keepintvl);
printf("TCP_KEEPCNT: %d\n", keepcnt);

close(fd);
return 0;
}
  1. 启用 keepalive 选项
    • setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)):启用 keepalive 选项。
  2. 设置 TCP_KEEPIDLE
    • setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)):设置发送 keepalive 报文的时间间隔为 3600 秒(1 小时)。
  3. 设置 TCP_KEEPINTVL
    • setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)):设置两次重试报文的时间间隔为 60 秒(1 分钟)。
  4. 设置 TCP_KEEPCNT
    • setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt)):设置重试次数为 5 次。
1
2
3
4
5
penge@penge-virtual-machine  ~/Desktop/mymuduo  sudo sysctl -a | grep keepalive
[sudo] password for penge:
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_keepalive_time = 7200

应用层 心跳包

从技术来讲,心跳包其实就是一个预先规定好格式的数据包,在程序中启动一个定时器,定时发送即可,这是最简单的实现思路。但是,如果通信的两端有频繁的数据来往,此时到了下一个发心跳包的时间点了,此时发送一个心跳包。这其实是一个流量的浪费,既然通信双方不断有正常的业务数据包来往,这些数据包本身就可以起到保活作用,为什么还要浪费流量去发送这些心跳包呢?所以,对于用于保活的心跳包,我们最佳做法是,设置一个上次包时间,每次收数据和发数据时,都更新一下这个包时间,而心跳检测计时器每次检测时,将这个包时间与当前系统时间做一个对比,如果时间间隔大于允许的最大时间间隔(实际开发中根据需求设置成 15 ~ 45 秒不等),则发送一次心跳包。总而言之,就是在与对端之间,没有数据来往达到一定时间间隔时才发送一次心跳包。心跳包机制设计详解

keep-alive无法做到这点,因此,应用层的心跳·机制有优势,但是应用层的心跳机需要新开辟线程完成心跳机制,因此这是缺点。

实现代码

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <iostream>
#include <thread>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

#define SERVER_IP "127.0.0.1"
#define PORT 12345

// 发送心跳包
void sendHeartBeat(int clientSock)
{
while (true)
{
std::this_thread::sleep_for(std::chrono::seconds(5)); // 每5秒发送一次心跳
std::string alive_msg = "ALIVE";
send(clientSock, alive_msg.c_str(), alive_msg.size(), 0);
std::cout << "Sent heartbeat to server." << std::endl;
}
}

int main()
{
int clientSock;
struct sockaddr_in serverAddr;

// 创建客户端 socket
clientSock = socket(AF_INET, SOCK_STREAM, 0);
if (clientSock < 0)
{
perror("Socket creation failed");
exit(EXIT_FAILURE);
}

// 设置服务器地址
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0)
{
perror("Invalid address");
close(clientSock);
exit(EXIT_FAILURE);
}

// 连接服务器
if (connect(clientSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
{
perror("Connection failed");
close(clientSock);
exit(EXIT_FAILURE);
}

std::cout << "Connected to the server." << std::endl;

// 启动心跳发送线程
std::thread heartBeatThread(sendHeartBeat, clientSock);

char buffer[1024];
while (true)
{
memset(buffer, 0, sizeof(buffer));
// 接收来自服务器的消息
int recvBytes = recv(clientSock, buffer, sizeof(buffer), 0);
if (recvBytes <= 0)
{
std::cout << "Disconnected from server." << std::endl;
break;
}

std::string message(buffer);
if (message == "HEARTBEAT")
{
std::cout << "Received heartbeat request from server." << std::endl;
std::string alive_msg = "ALIVE";
send(clientSock, alive_msg.c_str(), alive_msg.size(), 0);
}
}

// 关闭客户端连接
close(clientSock);
heartBeatThread.join();
return 0;
}

服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#include <iostream>
#include <thread>
#include <mutex>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

#define PORT 12345
#define MAX_CLIENTS 5
#define ALIVE_TIME_OUT 3 // 心跳超时次数
#define ALIVE_TIME_SPACE 10 // 心跳间隔时间(秒)

class ISvrNetEvent
{
public:
virtual void onHeartReq(int clientSock) = 0; // 发送心跳请求
virtual void onNetClose(int clientSock) = 0; // 网络关闭事件
};

// 心跳检测类
class HeartBeat
{
public:
HeartBeat(ISvrNetEvent *pAdapter, int clientSock)
: m_pNetAdapter(pAdapter), m_timeOutCount(0),
m_sendHearBeatCount(ALIVE_TIME_OUT), m_bIsVisible(true),
m_clientSock(clientSock) {}

void onLoop()
{
std::lock_guard<std::mutex> lock(m_mutex);
if (m_timeOutCount < ALIVE_TIME_SPACE && m_bIsVisible)
{
m_timeOutCount++;
}
else if (m_timeOutCount >= ALIVE_TIME_SPACE && m_bIsVisible)
{
if (m_pNetAdapter)
{
m_pNetAdapter->onHeartReq(m_clientSock);
}
m_timeOutCount = 0;
m_sendHearBeatCount--;
}

if (m_sendHearBeatCount <= 0 && m_bIsVisible && m_pNetAdapter)
{
m_bIsVisible = false;
m_pNetAdapter->onNetClose(m_clientSock);
}
}

// 收到心跳包时重置心跳计数
bool onRecvAlive()
{
std::lock_guard<std::mutex> lock(m_mutex);
m_sendHearBeatCount = ALIVE_TIME_OUT;
return true;
}

private:
ISvrNetEvent *m_pNetAdapter;
int m_timeOutCount;
int m_sendHearBeatCount;
bool m_bIsVisible;
int m_clientSock;
std::mutex m_mutex;
};

// 服务器处理类
class Server : public ISvrNetEvent
{
public:
Server(int port) : serverSock(-1), port(port) {}

// 启动服务器
void start()
{
struct sockaddr_in serverAddr;

// 创建服务器 socket
serverSock = socket(AF_INET, SOCK_STREAM, 0);
if (serverSock < 0)
{
perror("Socket creation failed");
exit(EXIT_FAILURE);
}

// 设置服务器地址
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(port);

// 绑定服务器 socket
if (bind(serverSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
{
perror("Bind failed");
close(serverSock);
exit(EXIT_FAILURE);
}

// 监听客户端连接
if (listen(serverSock, MAX_CLIENTS) < 0)
{
perror("Listen failed");
close(serverSock);
exit(EXIT_FAILURE);
}

std::cout << "Server listening on port " << port << std::endl;

// 接受客户端连接
while (true)
{
struct sockaddr_in clientAddr;
socklen_t clientLen = sizeof(clientAddr);
int clientSock = accept(serverSock, (struct sockaddr *)&clientAddr, &clientLen);
if (clientSock >= 0)
{
std::cout << "New client connected." << std::endl;
std::thread(&Server::handleClient, this, clientSock).detach();
}
}
}

// 处理客户端连接
void handleClient(int clientSock)
{
HeartBeat heartbeat(this, clientSock);

std::thread heartbeatThread([&]()
{
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
heartbeat.onLoop();
} });

char buffer[1024];
while (true)
{
memset(buffer, 0, sizeof(buffer));

// 接收来自客户端的消息
int recvBytes = recv(clientSock, buffer, sizeof(buffer), 0);
if (recvBytes <= 0)
{
std::cout << "Client disconnected." << std::endl;
break;
}

std::string message(buffer);
if (message == "ALIVE")
{
std::cout << "Received heartbeat from client." << std::endl;
heartbeat.onRecvAlive();
}
}

// 关闭客户端连接
close(clientSock);
heartbeatThread.join();
}

// 发送心跳请求
void onHeartReq(int clientSock) override
{
std::string heartReq = "HEARTBEAT";
send(clientSock, heartReq.c_str(), heartReq.size(), 0);
std::cout << "Sent heartbeat request to client." << std::endl;
}

// 处理网络关闭事件(超时)
void onNetClose(int clientSock) override
{
std::cout << "Client disconnected due to heartbeat timeout." << std::endl;
close(clientSock);
}

private:
int serverSock;
int port;
};

int main()
{
Server server(PORT);
server.start();
return 0;
}

本质就是开启线程,睡眠1s,然后取检测是否是否超时。我认为是需要维护个全局的时间轮才行。