今天查一个问题,反馈的日志中,看到服务端一个网络套接字sock 被设置成O_NONBLOCK了,但是在read的时候,报错了。
用strerror(errno),打印错误信息,看到是Connection timed out 。
这就诡异了,按理说,设置成O_NONBLOCK了,就不会有time out的,加日志调试的
struct timeval ti;
ti.tv_sec=0;
ti.tv_usec=0;
socklen_t len=sizeof(ti);
getsockopt(rcc->stream->socket,SOL_SOCKET,SO_RCVTIMEO,&ti,&len);
将这里的timeout超时时间也打印出来,看看是不是真有time out大于0那么邪门。调试时发现,这里ti.tv_sec 和 ti.tv_usec都是0。
那这个connect time out是什么意思?哪里报的错误?
从http://blog.csdn.net/u010419967/article/details/24405037,看到
Tcp是面向连接的,当tcp检测到对端socket不再可用时(不能发出探测包,或探测包没有收到ACK的响应包),
select会返回socket可读,并且在recv时返回-1,同时置上errno为ETIMEDOUT。
这个解释刚好和我们服务端的代码可以对得上。
原来在客户的应用场景,网络延时是比较大的(20ms左右),偶尔还有网络抖动。而且这个tcp连接还没有自己的心跳包,用的是tcp协议默认的心跳。
这个心跳值可以在代码设置,也可以在 /etc/sysctl.conf 设置
net.ipv4.tcp_keepalive_intvl = 30 这样就可以将该系统上运行的进程,tcp通信的描述为设置默认心跳为30秒。
因为我们的服务器没有配置这个项,所以就用了linux内核的默认值,Linux 2.6.24
#ifndef HZ
#define HZ 100
#endif
#define TCP_KEEPALIVE_INTVL(75*HZ)
所以是 7500秒,2个多小时才发出的一个心跳。在这种网络延时下,经过那么久才检查,tcp检测到对端socket 时,客户端的变成不再可用是很正常不过了。
怎么解决?
在客户端对sock套接字,加入心跳包,频繁一点检查,检查到断开了,就进行重新连接。
int keepAlive=1;//开启keepalive属性
int keepIdle=30;//如该连接在30秒内没有任何数据往来,则进行探测
int keepInterval=2;//探测时发包的时间间隔为2秒
int keepCount=3;//探测尝试的次数。如果第1次探测包就收到响应了,则后2次的不再发送
if(setsockopt(_peer,SOL_SOCKET,SO_KEEPALIVE,(void *)&keepAlive,sizeof(keepAlive))!=0)//若无错误发生,setsockopt()返回值为0
{
LOG_INFO(" set_keepalive set SO_KEEPALIVE fail");
return ;
}
if(setsockopt(_peer,SOL_TCP,TCP_KEEPIDLE,(void *)&keepIdle,sizeof(keepIdle))!=0)
{
LOG_INFO("set_keepalive set TCP_KEEPIDLE fail");
return ;
}
if(setsockopt(_peer,SOL_TCP,TCP_KEEPINTVL,(void *)&keepInterval,sizeof(keepInterval))!=0)
{
LOG_INFO("set_keepalive set TCP_KEEPINTVL fail");
return ;
}
if(setsockopt(_peer,SOL_TCP,TCP_KEEPCNT,(void *)&keepCount,sizeof(keepCount))!=0)
{
LOG_INFO(" set_keepalive set TCP_KEEPCNT fail");
return ;
}