目录
2、TCPIP协议栈的心跳机制
2.2、TCPIP协议栈的心跳机制说明
2.3、修改TCPIP协议栈的默认心跳参数
3、libwebsockets开源库中的心跳机制使用的就是TCPIP协议栈的心跳机制
5、使用非阻塞socket和select接口实现connect连接的超时控制
5.1、MSDN上对connect和select接口的说明
5.2、使用非阻塞socket和select实现连接超时的控制
最近有个项目,客户的网络环境不太稳定,会时不时出现丢包和网络抖动的情况,导致我们软件客户端会时不时和服务器断链,导致正在进行中的视频会议会被中断。本文借此机会,结合具体的问题实例,详细讲解一下TCP/IP协议栈的 心跳机制 、 丢包重传机制 等内容,给大家提供一个借鉴和参考。
1、问题概述
虽然软件底层模块在网络恢复后能自动重连上服务器,但会议因为网络问题已经退出,需要重新加入会议。因为客户特殊的网络运行环境,会频繁出现网络抖动不稳定的情况,客户要求必须要实现60秒内网络恢复后能依然保持在会议中,保证会议流程不被中断。
客户坚持要实现这个特殊的功能点,项目已经接近尾声,目前处于客户试用阶段,不实现该功能,项目无法通过验收,客户不给钱。
前方同事将当前问题及项目进展情况向研发部门领导反馈,研发部紧急召开讨论会议,商讨60秒不掉会的实现方案。这里面涉及到两大类的网络连接,一类是传输控制信令的 TCP连接 ,另一类是传输音视频码流的 UDP连接 。UDP连接的问题不大,主要是TCP连接的断链与重连问题,下面主要讨论TCP连接相关问题。
在出现网络不稳定掉会时,可能是系统TCPIP协议栈已经检测到网络异常,系统协议层已经将网络断开了;也可能软件应用层的心跳机制检测到网络故障,断开了与服务器的链接。
对于系统TCPIP协议栈自身检测出来的网络异常,则可能存在两种情况,一是TCPIP协议栈自身的心跳机制检测出来的;二是TCP连接的丢包重传机制检测出异常。
对于应用层的心跳检测机制,我们可以放大超时检测时间。 本文我们主要讨论一下TCPIP协议栈的TCP连接的心跳、丢包重传、连接超时等机制 。在检测到网络异常后,我们底层可以自动发起重连或者信令发送触发自动重连,业务模块将会议相关资源保存不释放,在网络恢复后可以继续保持在会议中,可以继续接收到会议中的音视频码流,可以继续进行会议中的一些操作!
2、TCPIP协议栈的心跳机制
2.1、TCP中的ACK机制
TCP建链时的三次握手流程如下所示:
之所以说TCP连接是可靠的,首先是发送数据前要建立连接,再就是收到数据后都会给对方恢复一个ACK包,表明我收到你的数据包了。对于数据发送端,如果数据发出去后没有收到ACK包,则会触发丢包重传机制。
不管是建链时,还是建链后的数据收发时,都有ACK包,TCPIP协议栈的心跳包也不例外。
2.2、TCPIP协议栈的心跳机制说明
TCPIP协议栈有个默认的 TCP心跳机制 ,这个心跳机制是和 socket套接字 (TCP套接字)绑定的,可以对指定的套接字开启协议栈的心跳检测机制。 默认情况下,协议栈的心跳机制对socket套接字是关闭的 ,如果要使用需要人为开启的。
在Windows中,默认是每隔2个小时发一次心跳包,客户端程序将心跳包发给服务器后,接下来会有两种情况:
1) 网络正常时: 服务器收到心跳包,会立即回复ACK包,客户端收到ACK包后,再等2个小时发送下一个心跳包。其中,心跳包发送时间间隔时间keepalivetime,Windows系统中默认是2小时,可配置。如果在2个小时的时间间隔内,客户端和服务器有数据交互,客户端会收到服务器的ACK包,也算作心跳机制的心跳包,2个小时的时间间隔会重新计时。
2) 网络异常时: 服务器收不到客户端发过去的心跳包,没法回复ACK,Windows系统中默认的是1秒超时,1秒后会重发心跳包。如果还收不到心跳包的ACK,则1秒后重发心跳包,如果始终收不到心跳包,则在发出10个心跳包就达到了系统的上限,就认为网络出故障了,协议栈就会直接将连接断开了。其中,发出心跳包收不到ACK的超时时间称为keepaliveinterval,Windows系统中默认是1秒,可配置;收不到心跳包对应的ACK包的重发次数probe,Windows系统是固定的,是固定的10次,不可配置的。
所以TCPIP协议栈的心跳机制也能检测出网络异常,不过在默认配置下可能需要很久才能检测出来,除非网络异常出现在正在发送心跳包后等待对端的回应时,这种情况下如果多次重发心跳包都收不到ACK回应,协议栈就会判断网络出故障,主动将连接关闭掉。
2.3、修改TCPIP协议栈的默认心跳参数
TCPIP协议栈的默认心跳机制的开启, 不是给系统整个协议栈开启心跳监测 ,而是对某个socket套接字开启。
开启心跳机制后,还可以修改心跳的时间参数。从代码上看,先调用setsockopt给目标套接字开启心跳监测机制,再调用WSAIoctl去修改心跳检测的默认时间参数,相关代码如下所示:
SOCKET socket;
// ......(中间代码省略)
int optval = 1;
int nRet = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char *)&optval,
sizeof(optval));
if (nRet != 0)
return;
tcp_keepalive alive;
alive.onoff = TRUE;
alive.keepalivetime = 10*1000;
alive.keepaliveinterval = 2*1000;
DWORD dwBytesRet = 0;
nRet = WSAIoctl(socket, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), NULL, 0,
&dwBytesRet, NULL, NULL);
if (nRet != 0)
return;
上面的代码可以看到,先调用setsockopt函数,传入SO_KEEPALIVE参数,打开TCP连接的心跳开关,此时心跳参数使用系统默认的心跳参数值。紧接着,调用WSAIoCtrl函数,传入SIO_KEEPALIVE_VALS参数,同时将设置好时间值的心跳参数结构体传进去。</