深入剖析阻塞式socket的timeout

本文详细探讨了阻塞式socket在连接、发送和接收时的超时问题。连接超时涉及内核层的超时分析,包括SYN报文重试机制和超时时间计算。发送和接收超时同样依赖于内核的超时管理,涉及重传定时器和TCP_RTO_MIN参数。此外,讨论了对端物理机宕机和进程宕机时的超时处理。建议在实际应用中合理设置超时时间,避免长时间等待影响系统性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、前言


网络编程中超时时间是一个重要但又容易被忽略的问题,对其的设置需要仔细斟酌。

本文讨论的是socket设置为阻塞模式,如果socket处于阻塞模式运行时,就需要考虑处理socket操作超时的问题。所谓阻塞模式,是指其完成指定的操作之前阻塞当前的进程或线程,直到操作有结果返回.在我们直接调用socket操作函数时,如果不进行特意声明的话,它们都是工作在阻塞模式的,如 connect, send, recv等.

简单分类的话,可以将超时处理分成两类:

连接(connect)超时;发送(send), 接收(recv)超时;

2、连接超时


从字面上看,连接超时就是在一定时间内还是连接不上目标主机。你所建立的socket连接其实最终都要进行系统调用进入内核态,剩下的就是等待内核通知连接建立。所以自行在代码中设置了超时时间(一般是叫connectTimeout或者socketTimeout),那么这个超时时间一到如果内核还没成功建立连接,那就认为是连接超时了。如果他们没设置超时时间,那么这个connectTimeout就取决于内核什么时候抛出超时异常了。

因此,我们需要分析一下内核是怎么来判断连接超时的。

2.1内核层的超时分析

我们都知道一个连接的建立需要经过3次握手,所以连接超时简单的说是是客户端往服务端发的SYN报文没有得到响应(服务端没有返回ACK报文)。

由于网络本身是不稳定的,丢包是很常见的事情(或者对方主机因为某些原因丢弃了该包),因此内核在发送SYN报文没有得到响应后,往往还是进行多次重试。同时,为了避免发送太多的包影响网络,重试的时间间隔还会不断增加。

在linux中,重试的时间间隔会呈指数型增长,为2的N次方,即:

第一次发送SYN报文后等待1s(2的0次幂)后再重试

第二次发送SYN报文后等待2s(2的1次幂)后再重试

第三次发送SYN报文后等待4s(2的2次幂)后再重试

第四次发送SYN报文后等待8s(2的3次幂)后再重试

第五次发送SYN报文后等待16s(2的4次幂)后再重试

第六次发送SYN报文后等待32s(2的5次幂)后再重试

第七次发送SYN报文后等待64s(2的6次幂)后再重试

对于重试次数,由linux的net.ipv4.tcp_syn_retries来确定,默认值一般是6(有些linux发行版可能不太一样),我们可以通过sysctl net.ipv4.tcp_syn_retries查看。比如重试次数是6次,那么我们可以得出超时时间应该是 1+2+4+8+16+32+64=127秒 (上面的第一条是第一次发送SYN报文,不算重试)。

如果我们想修改重试次数,可以输入命令sysctl -w net.ipv4.tcp_syn_retries=5来修改(需要root权限)。如果希望重启后生效,将net.ipv4.tcp_syn_retries = 5放入/etc/sysctl.conf中,之后执行sysctl -p 即可生效。

在一些linux发行版中,重试时间可能会变动。如果想确定操作系统具体的超时时间,可以通过下面这条命令来判断:

gaoke@ubuntu:~$ date; telnet 10.16.15.15 5000; date

Sat Apr 2 14:27:33 CST 2022

Trying 10.16.15.15...

telnet: Unable to connect to remote host: Connection timed out

Sat Apr 2 14:29:40 CST 2022

2.2综合分析

如果应用层面设置了自己的超时时间,同时内核也有自己的超时时间,那么应该以哪个为准呢?答案是哪个超时时间小以哪个为准。

个人认为,在我们的实际应用中,这个超时时间不宜设置的太长,通常建议2-10s。比如在分布式系统中,我们通常会在多台节点中根据一定策略选择一台进行连接。在有机器宕机的情况下,如果连接超时时间设置的比较长,而我们客户端的线程池又比较小,就很可能大多数的线程都在等待建立连接,过了较长时间才发现连接不上,影响应用的整体吞吐量。

2.3connect系统调用

我们观察一下此系统调用的kernel源码,调用栈如下所示:

connect[用户态]

|->SYSCALL_DEFINE3(connect)[内核态]

|->sock->ops->connect

最终调用的tcp_connect源码如下:

int tcp_connect(struct sock *sk) {

......

// 发送SYN

err = tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);

...

/* Timer for repeating the SYN until an answer. */

// 由于是刚建立连接,所以其rto是TCP_TIMEOUT_INIT

inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,

inet_csk(sk)->icsk_rto, TCP_RTO_MAX);

return 0;

}

又上面代码可知,在tcp_connect设置了重传定时器之后return回了tcp_v4_connect再return到inet_stream_connect。

我们可以采用设置SO_SNDTIMEO来控制connect系统调用的超时,如下所示:

setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);

2.4不设置SO_SNDTIMEO

如果不设置SO_SNDTIMEO,那么会由tcp重传定时器在重传超过设置的时候后超时,如下图所示:

我们如何查看syn重传次数?:

cat /proc/sys/net/ipv4/tcp_syn_retries

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值