心跳包的发送,通常有两种技术:
方法1:应用层自己实现的心跳包
由应用程序自己发送心跳包来检测连接是否正常,大致的方法是:服务器在一个 Timer事件中定时向客户端发送一个短的数据包,然后启动一个线程,在该线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为服务端异常。
方法2:TCP的KeepAlive保活机制
因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多且稍显复杂,而利用TC/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。 因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功 能,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,KeepAlive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接。并且,默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数。
使用TCP的KeepAlive保活机制
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <MSTcpIP.h>
#pragma comment(lib,"ws2_32.lib")
.....
SOCKET serverSocket;
serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET)
{
printf("socket failed!\n");
return 0;
}
DWORD kaEnable = 1;
if ((setsockopt(serverSocket, SOL_SOCKET, SO_KEEPALIVE, (PCHAR)&kaEnable, sizeof(kaEnable))))
{
puts("setsockopt error!");
}
tcp_keepalive ka;
ka.onoff = 1;
ka.keepalivetime = 1000 * 20;
ka.keepaliveinterval = 1000 * 5;
DWORD cb = 0;
if ((WSAIoctl(serverSocket, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &cb, NULL, NULL)))
{
puts("WSAIoctl error!");
}
if (bind(serverSocket, (sockaddr*)&servAddr, sizeof(sockaddr)) == SOCKET_ERROR)
{
printf("ERROR:Bind failed\n");
return 0;
}
if (listen(serverSocket, 20) == SOCKET_ERROR)
{
closesocket(serverSocket);
WSACleanup();
printf("ERROR:Listen failed\n");
return 0;
}
printf("linstening:%d port...\n", ntohs(servAddr.sin_port));
int len = sizeof(cliAddr);
SOCKET clientSocket;
clientSocket = accept(serverSocket, (sockaddr*)&cliAddr, &len);
printf("Connected: %s \r\n", inet_ntoa(cliAddr.sin_addr));
char recvBuf[100] = { 0 };
recv(clientSocket, recvBuf, sizeof(recvBuf), 0);
.....
- 需要引入头文件 MSTcpIP.h
- keepalivetime 的值表示客户端与服务器之间没有数据交互后,发送心跳所间隔的时间(毫秒),keepaliveinterval 表示再未收到确认心跳后,再次发送心跳所间隔的时间(毫秒)。
参考:https://docs.microsoft.com/zh-cn/windows/win32/winsock/so-keepalive