没有神的光环,你孤独而平凡~
心跳机制作用:
- 死链是指在网络通信中,由于底层网络问题导致数据无法正常传输,但连接状态仍然保持的一种异常情况。通过心跳检测和超时机制,可以有效检测和处理死链,确保连接的可靠性和资源的有效利用。
- 保活
传输层 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); }
int on = 1; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) < 0) { perror("setsockopt SO_KEEPALIVE"); close(fd); exit(EXIT_FAILURE); }
int keepidle = 3600; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)) < 0) { perror("setsockopt TCP_KEEPIDLE"); close(fd); exit(EXIT_FAILURE); }
int keepintvl = 60; if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)) < 0) { perror("setsockopt TCP_KEEPINTVL"); close(fd); exit(EXIT_FAILURE); }
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; }
|
- 启用 keepalive 选项:
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on))
:启用 keepalive 选项。
- 设置 TCP_KEEPIDLE:
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle))
:设置发送 keepalive 报文的时间间隔为 3600 秒(1 小时)。
- 设置 TCP_KEEPINTVL:
setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl))
:设置两次重试报文的时间间隔为 60 秒(1 分钟)。
- 设置 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)); 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;
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;
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);
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,然后取检测是否是否超时。我认为是需要维护个全局的时间轮才行。