当客户端与服务端保持长连接时,客户端除了可以使用心跳包来确认连接状态外,还可以使用一下几种方法:
一、使用recv() 函数判断连接状态
使用recv()
函数的MSG_PEEK
选项,使用这个选项可以“偷看(peek)”一下接收队列的数据。所谓“偷看”,就是recv()
不从接收队列中把数据移除,因此下一次调用recv()
函数还会接收到此数据。
当TCP连接处于已建立Established
状态时,如果接收队列中有数据,recv()
函数返回1;如果接收队列中没有数据但是TCP连接没有断开,recv()
函数返回-1,并且把errno
设置为EAGAIN
。其余情况下表示TCP连接已经断开。
bool
tcp_connection_state(int fd) {
char buf[1];
int err;
err = recv(fd, buf, sizeof(buf), MSG_PEEK);
if (err == 1 || (err == -1 && errno == EAGAIN)) {
return true;
}
return false;
}
PS: 注意在获取errno
时,也可以使用getsockopt(fd, SOL_SOCKET, SO_ERROR, &status, &status_len)
来获取,SO_ERROR
选项获得errno
的值并且随后置零。
二、使用epoll异步监控连接状态
使用epoll监控socket连接时有两种情况下可以断定此连接已经断开:
- 当触发可读事件,但是接收字节数为0。这种情况下,FIN数据包在
read()
调用前,在EPOLLIN
唤醒epoll后到达TCP被动关闭方。 - 当
EPOLLRDHUP
事件唤醒epoll_wait()
函数,表示对端主动断开连接。注意当epoll事件返回为EPOLLHUP
通常时socket内部错误,而不是连接主动断开。
三、使用getsockopt() 函数的TCP_INFO
选项
bool
tcp_connection_state(int fd) {
struct tcp_info info;
int len=sizeof(info);
getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
if((info.tcpi_state==TCP_ESTABLISHED)) {
return true;
}
return false;
}
在没有使用read()
、write()
、epoll()
时使用
四、使用TCP心跳处理
int keepAlive = 1; // 开启keepalive属性
int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测
int keepInterval = 5; // 探测时发包的时间间隔为5 秒
int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
设置后,若断开,则在使用该socket读写时立即失败,并返回ETIMEDOUT错误。但是上述方法有个重试次数,tcp连接状态检测检测有滞后性。在linux kernel 2.6.37版本后,支持TCP_USER_TIME_OUT
选项,超时后直接判断连接超时。
unsigned int timeout = 1000; //值为数据包被发送后未接收到ACK确认的最大时长,以毫秒
//为单位,例如设置为timeout时,代表如果发送出去的数据包
//在timeout毫秒内未收到ACK确认,则下一次调用send或者recv,
//函数会返回-1,errno设置为ETIMEOUT,代表connection timeout。
//使用TCP Keep-alive加上TCP_USER_TIMEOUT机制,就可以完美解
//决通信对端异常断网、掉电的情况下,连接被长期挂起的问题了。
setsockopt(socket_fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
五、使用七层心跳处理
有时服务端程序停止服务,但是内核还维护着TCP连接。在这种情况下,使用7层的心跳处理是有效的,
参考
大家对此问题有什么想法,欢迎讨论。加油,打工人!