
TCP Keep-Alive 的原理
一个 TCP 连接上,如果通信双方都不向对方发送数据,那么 TCP 连接就不会有任何数据交换。
假设应用程序是一个 web 服务器,客户端发出三次握手(关于握手与挥手的详细过程,可以参看本站的《三次握手与四次挥手》)以后故障宕机或被踢掉网线,对于 web 服务器而言,下一个数据包将永远无法到来,但是它一无所知。TCP Keep-Alive 机制就是为了解决这个问题。顺带一提,拔掉网线的瞬间连接并不会立即断开,具体原因可以看本站的《拔掉网线后,TCP 连接还在吗》。
TCP Keep-Alive 机制就是一种确保 TCP 连接有效性的方法,主要用于检测长时间没有数据交换的连接是否仍然存活。
它的作用就是探测对端的连接有没有失效:通过定时发送探测包来探测连接的对端是否存活。不过默认情况下需要 7200s 没有数据包交互才会发送 keepalive 探测包,往往这个时间太久了,我们熟知的很多组件都没有开启 keepalive 特性,而是选择在应用层做心跳机制。
一般情况下,做服务器开发,都会在应用层自己做心跳机制。例如一个心跳包结构体:
enum MsgType{
HEARTBEAT = 10,
LOGIN = 11,
LOGOUT = 12,
...
};
struct MyHeader{
MyHeader()
{
memset(this, 0, sizeof(*this));
usLen = sizeof(*this);
}
uint16_t usLen;
int cType;
};
struct MyMsgHeart : public MyHeader{
MyMsgHeart()
{
memset(this, 0, sizeof(MyMsgHeart));
usLen = sizeof(MyMsgHeart);
cType = HEARTBEAT;
}
};
TCP Keep-Alive 系统内核参数
查询命令如下:
sysctl -a | grep keepalive
三个参数的含义如下:
net.ipv4.tcp_keepalive_intvl = 75 //没有接收到对方确认,继续发送保活探测包的发送频率,默认值为75s
net.ipv4.tcp_keepalive_probes = 9 //没有接收到对方确认,继续发送保活探测包次数
net.ipv4.tcp_keepalive_time = 7200 //在TCP保活打开的情况下,最后一次数据交换到TCP发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为7200s(2h)
TCP Keep-Alive 不是默认启用的,需要在创建套接字时显式设置,通常是通过套接字选项 SO_KEEPALIVE 来开启。
C/C++ 中,通过 setsockopt 设置 keepalive 开关(默认 socket 是关闭 keepalive 的):
int optval = 1;
::setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY,
&optval, static_cast<socklen_t>(sizeof optval));
::setsockopt(hSocket, SOL_SOCKET, SO_REUSEADDR,
&optval, static_cast<socklen_t>(sizeof optval));
::setsockopt(hSocket, SOL_SOCKET, SO_KEEPALIVE,
&optval, static_cast<socklen_t>(sizeof optval)); //这里打开Keep-Alive
注意这里只是打开了 SO_KEEPALIVE 开关,探测的节奏并不会随之改变:不做额外设置的话,内核就按上面三个参数的默认值来工作——7200s 空闲后开始探测,每 75s 一次,共 9 次。示例里顺带设置的 TCP_NODELAY、SO_REUSEADDR 与保活无关。如果想在套接字级别自定义探测节奏,可以用 TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT 这三个选项(IPPROTO_TCP 层级)分别覆盖空闲时长、探测间隔和探测次数。