在网络通信中很多操作都是默认阻塞的,比如 recv函数,当接收缓冲区中的数据没有达到水位线时,上层会一直处在阻塞等待数据就绪的状态。出现这种情况的原因有两个,一个是数据没有就绪,一个是网络连接断开。
目录
1、网络超时检测
(1) 设置套接字属性
(2) 通过select模型检测
(3) 信号检测
2、心跳检测
(1) 定期发送检测报文
(2) 设置套接字属性
可以使用 setsockopt 函数来设置套接字属性,setsockopt函数的第三个参数有一个 SO_RCVTIMEO 选项来设置接收超时时间。
struct timeval tout;
tout.tv_sec = 5;
tout.tv_usec = 0;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(struct timeval));
recv(); // 从接收缓冲区读取数据
select模型是五种IO模型中的一种,它会逐个遍历每一个文件描述符,并判断文件描述符是否读就绪或者写就绪。
struct fd_set rdfs;
struct timeval tv = {5, 0}; // 设置select模型阻塞等待时间
FD_ZERO(&rdfs);
FD_SET(sockfd, &rdfs);
if(select(sockfd+1, &rdfs, NULL, NULL, &tv) > 0){
recv(); // 从接收缓冲区读取数据
}
我们可以设置定时器,每隔一定时间向当前进程发送 SIGALRM 信号,此时我们的进程需要做如下两件事。
void sigHandler(int signum){
return;
}
int main(){
// 先获取到原本的信号处理配置
struct sigaction act;
sigaction(SIGALRM, NULL, &act);
// 设置新的信号处理配置
act.sa_handler = sigHandler;
act.sa_flags |= SA_RESTART;
sigaction(SIGALRM, &act, NULL);
// 设置定时器,每隔5s发送一次 SIGALRM 信号
alarm(5);
// 从缓冲区中读取数据
recv();
}
以客户端和服务端的交互为例。如果服务端在一段时间里没有收到对方发来的报文,此时服务端可以发送一个检测报文(数据可以是空的)。
setsockopt函数的第三个参数有一个可选值为SO_KEEPALIVE,表示保持连接,设置该选项以后,当前进程会每隔2个小时检测一下连接是否断开,因为2个小时的时间间隔太长了,所以我们还需要设置其他选项来控制检测时间间隔。
setsockopt的第二个参数 | setsockopt的第三个参数 | 含义 |
SOL_SOCKET | SO_KEEPALIVE | 设置套接字处于保持连接的状态,每隔一定时间检测连接是否断开 |
SOL_TCP | TCP_KEEPIDLE | 从设置当前选项起,多长时间以后,第一次检测连接状态 |
SOL_TCP | TCP_KEEPINTVL | 自第一次检测以后,每隔多长时间检测一次连接状态 |
SOL_TCP | TCP_KEEPCNT | 判定连接断开时,KeepAlive的检测次数。比如设为5,表示连续5次检测连接状态都是断开的,此时判定连接断开 |
下面是封装好的心跳检测函数:
/**
* 通过设置套接字属性来定期检测连接状态
*
* @param sockfd 套接字
* @param attr_on 套接字属性的值(1 表示设置保持连接;0表示不保持连接)
* @param idle_time 多长时间以后开始第一次连接状态检测
* @param interval 自第一次连接状态检测以后,每隔多长时间检测一次
* @param cnt 检测多少次才判定连接断开
*/
void setKeepAlive(int sockfd,
int attr_on,
socklen_t idle_time,
socklen_t interval,
socklen_t cnt);
使用时注意传入的参数
int keepAlive = 1; // 设定是否保持连接
int keepIdle = 5; // 多长时间以后开始第一次检测
int keepInterval = 5; // 自第一次检测以后,每隔多长时间检测一次
int keepCount = 3; // 判定连接断开的检测次数
setKeepAlive(sockfd, keepAlive, keepIdle, keepInterval, keepCount);