深入剖析阻塞式socket的timeout

这篇具有很好参考价值的文章主要介绍了深入剖析阻塞式socket的timeout。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

最终调用的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重传定时器在重传超过设置的时候后超时,如下图所示:

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

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

cat /proc/sys/net/ipv4/tcp_syn_retries

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

对于系统调用,connect的超时时间为:

tcp_syn_retries

timeout

1

min(so_sndtimeo,3s)

2

min(so_sndtimeo,7s)

3

min(so_sndtimeo,15s)

4

min(so_sndtimeo,31s)

5

min(so_sndtimeo,63s)

kernel代码版本细微变化

值得注意的是,linux本身官方发布的2.6.32源码对于tcp_syn_retries2的解释和RFC并不一致,不同内核小版本上的实验会有不同的connect timeout表现的原因(有的抓包到的重传SYN时间间隔为3,6,12......)。

以下为代码对比:

========================>linux 内核版本2.6.32-431<========================

#define TCP_TIMEOUT_INIT ((unsigned)(1*HZ)) /* RFC2988bis initial RTO value */

static inline bool retransmits_timed_out(struct sock *sk,

unsigned int boundary,

unsigned int timeout,

bool syn_set)

{

......

unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN;

......

timeout = ((2 << boundary) - 1) * rto_base;

......

}

========================>linux 内核版本2.6.32.63<========================

#define TCP_TIMEOUT_INIT ((unsigned)(3*HZ)) /* RFC 1122 initial RTO value */

static inline bool retransmits_timed_out(struct sock *sk,

unsigned int boundary

{

......

timeout = ((2 << boundary) - 1) * TCP_RTO_MIN;

......

}

另外,tcp_syn_retries重传次数可以在单个socket中通过setsockopt设置。

3、发送超时


在tcp连接建立之后,写操作可以理解为向对端发送tcp报文的过程。在tcp的实现中,每一段报文都需要有对端的回应,即ACK报文。和连接时发送SYN报文一样,如果超过一定时间没有收到响应,内核会再次重发该报文。和SYN报文的重试不同的是,linux有另外的参数来控制这个重试次数,即net.ipv4.tcp_retries2,可以通过sysctl net.ipv4.tcp_retries2查看其值。

另外,这个数据报文重试时间间隔的计算方式也和SYN报文不一样,由于计算方式比较复杂,这里就不详细介绍。

一般linux发行版的net.ipv4.tcp_retries2的默认值为5或者15,对应的超时时间如下表:

tcp_retries2对端无响应

525.6s-51.2s,根据动态rto定

15924.6s-1044.6s,根据动态rto定

和SYN报文的超时时间一样,如果应用层设置了超时时间,哪么具体的超时时间以内核和应用层的超时时间的最小值为准。

socket的write系统调用最后调用的是tcp_sendmsg,源码如下所示:

int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,

size_t size){

......

timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);

......

while (--iovlen >= 0) {

......

// 此种情况是buffer不够了

if (copy <= 0) {

new_segment:

......

if (!sk_stream_memory_free(sk))

goto wait_for_sndbuf;

skb = sk_stream_alloc_skb(sk, select_size(sk),sk->sk_allocation);

if (!skb)

goto wait_for_memory;

}

......

}

......

// 这边等待write buffer有空间

wait_for_sndbuf:

set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);

wait_for_memory:

if (copied)

tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);

// 这边等待timeo长的时间

if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)

goto do_error;

......

out:

// 如果拷贝了数据,则返回

if (copied)

tcp_push(sk, flags, mss_now, tp->nonagle);

TCP_CHECK_TIMER(sk);

release_sock(sk);

return copied;

out_err:

// error的处理

err = sk_stream_error(sk, flags, err);

TCP_CHECK_TIMER(sk);

release_sock(sk);

return err;

}

从上面的内核代码看出,如果socket的write buffer依旧有空间的时候,会立马返回,并不会有timeout。但是write buffer不够的时候,会等待SO_SNDTIMEO的时间(nonblock时候为0)。但是如果SO_SNDTIMEO没有设置的时候,默认初始化为MAX_SCHEDULE_TIMEOUT,可以认为其超时时间为无限。那么其超时时间会有另一个条件来决定,我们看下sk_stream_wait_memory的源码:

int sk_stream_wait_memory(struct sock *sk, long *timeo_p){

// 等待socket shutdown或者socket出现err

sk_wait_event(sk, ¤t_timeo, sk->sk_err ||

(sk->sk_shutdown & SEND_SHUTDOWN) ||

(sk_stream_memory_free(sk) &&

!vm_wait));

}

在write等待的时候,如果出现socket被shutdown或者socket出现错误的时候,则会跳出wait进而返回错误。在不考虑对端shutdown的情况下,出现sk_err的时间其实就是其write的timeout时间,那么我们看下什么时候出现sk->sk_err。

SO_SNDTIMEO不设置,write buffer满之后ack一直不返回的情况(例如,物理机宕机)

物理机宕机后,tcp发送msg的时候,ack不会返回,则会在重传定时器tcp_retransmit_timer到期后timeout,其重传到期时间通过tcp_retries2以及TCP_RTO_MIN计算出来。

tcp_retries2的设置位置为:

cat /proc/sys/net/ipv4/tcp_retries2

SO_SNDTIMEO不设置,write buffer满之后对端不消费,导致buffer一直满的情况

和上面ack超时有些许不一样的是,一个逻辑是用TCP_RTO_MIN通过tcp_retries2计算出来的时间。另一个是真的通过重传超过tcp_retries2次数来time_out,两者的区别和rto的动态计算有关。但是可以大致认为是一致的。

上述逻辑如下图所示:

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

write_timeout表格

tcp_retries2

buffer未满

buffer满

5

立即返回

min(SO_SNDTIMEO,(25.6s-51.2s)根据动态rto定

15

立即返回

min(SO_SNDTIMEO,(924.6s-1044.6s)根据动态rto定

4、接收超时


在tcp协议中,读的操作和写操作的逻辑是相通的。

tcp连接建立后,两边的通信无非就是报文的互传。写操作是将数据放到tcp报文中发送给对端,然后等待对端响应,一定时间没有得到响应就是超时。而读操作其实就是发送一个读取数据的报文给对端,然后对端返回带有数据的报文,一定时间没有收到对端的报文则认为超时。对于tcp协议而言,其实不会分辨他们发送的报文具体是要干嘛,因此readTimeout的判断逻辑和writeTimeout基本一样。它的重传次数也是由参数net.ipv4.tcp_retries2控制。在应用层面也一般是统一叫socketTimeout。

read系统调用

socket的read系统调用最终调用的是tcp_recvmsg, 其源码如下:

int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,

size_t len, int nonblock, int flags, int *addr_len)

{

......

// 这边timeo=SO_RCVTIMEO

timeo = sock_rcvtimeo(sk, nonblock);

......

do{

......

// 下面这一堆判断表明,如果出现错误,或者已经被CLOSE/SHUTDOWN则跳出循环

if(copied) {

if (sk->sk_err ||

sk->sk_state == TCP_CLOSE ||

(sk->sk_shutdown & RCV_SHUTDOWN) ||

!timeo ||

signal_pending(current))

break;

} else {

if (sock_flag(sk, SOCK_DONE))

break;

if (sk->sk_err) {

copied = sock_error(sk);

break;

}

// 如果socket shudown跳出

if (sk->sk_shutdown & RCV_SHUTDOWN)

break;

// 如果socket close跳出

if (sk->sk_state == TCP_CLOSE) {

if (!sock_flag(sk, SOCK_DONE)) {

/* This occurs when user tries to read

* from never connected socket.

*/

copied = -ENOTCONN;

break;

}

break;

}

.......

}

.......

if (copied >= target) {

/* Do not sleep, just process backlog. */

release_sock(sk);

lock_sock(sk);

} else /* 如果没有读到target自己数(和水位有关,可以暂认为是1),则等待SO_RCVTIMEO的时间 */

sk_wait_data(sk, &timeo);

} while (len > 0);

......

}

上面的逻辑如下图所示:

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

重传以及探测定时器timeout事件的触发时机如下图所示:

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

如果内核层面ack正常返回而且对端窗口不为0,仅仅应用层不返回任何数据,那么就会无限等待,直到对端有数据或者socket close/shutdown为止,如下图所示:

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

很多应用就是基于这个无限超时来设计的,例如activemq的消费者逻辑。

ReadTimeout超时表格

C系统调用:

tcp_retries2

对端无响应

对端内核响应正常

5

min(SO_RCVTIMEO,(25.6s-51.2s)根据动态rto定

SO_RCVTIMEO==0?无限,SO_RCVTIMEO)

15

min(SO_RCVTIMEO,(924.6s-1044.6s)根据动态rto定

SO_RCVTIMEO==0?无限,SO_RCVTIMEO)

Java系统调用

tcp_retries2

对端无响应

对端内核响应正常

5

min(SO_TIMEOUT,(25.6s-51.2s)根据动态rto定

SO_TIMEOUT==0?无限,SO_RCVTIMEO

15

min(SO_TIMEOUT,(924.6s-1044.6s)根据动态rto定

SO_TIMEOUT==0?无限,SO_RCVTIMEO

5、对端物理机宕机之后的超时


对端物理机宕机后还依旧有数据发送

对端物理机宕机时对端内核也gg了(不会发出任何包通知宕机),那么本端发送任何数据给对端都不会有响应。其超时时间就由上面讨论的min(设置的socket超时[例如SO_TIMEOUT],内核内部的定时器超时来决定)。

对端物理机宕机后没有数据发送,但在read等待

这时候如果设置了超时时间timeout,则在timeout后返回。但是,如果仅仅是在read等待,由于底层没有数据交互,那么其无法知道对端是否宕机,所以会一直等待。但是,内核会在一个socket两个小时都没有数据交互情况下(可设置)启动keepalive定时器来探测对端的socket。如下图所示:

sockettimeout,音视频开发,websocket,网络,音视频,Powered by 金山文档

大概是2小时11分钟之后会超时返回。keepalive的设置由内核参数指定:

cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 即两个小时后开始探测

cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 即每次探测间隔为75s

cat /proc/sys/net/ipv4/tcp_keepalve_probes 9 即一共探测9次

可以在setsockops中对单独的socket指定是否启用keepalive定时器。

对端物理机宕机后没有数据发送,也没有read等待

和上面同理,也是在keepalive定时器超时之后,将连接close。所以我们可以看到一个不活跃的socket在对端物理机突然宕机之后,依旧是ESTABLISHED状态,过很长一段时间之后才会关闭。

6、进程宕机后的超时


物理机突然宕机和进程宕掉的表现不一样。一个tcp连接建立后,如果一端的物理机突然宕机,另外一端是完全不知情的,它会像往常一样继续发送相关报文,直到超时时间到了才返回。另外,一般操作系统会有机制检测来释放该tcp连接。而如果只是进程宕掉,在进程退出的时候,操作会负责回收这个进程所属的所有tcp连接,在这时会向这些tcp连接的对端发送FIN报文,表示要关闭连接了,这时候对端是可以知道连接已经关闭的。(如果进程退出后还收到来自对端的报文,那么内核会立马发送reset给对端,从而不会卡住对端的线程资源)

所以如果仅仅是对端进程宕机的话(进程所在内核会close其所拥有的所有socket),由于fin包的发送,本端内核可以立刻知道当前socket的状态。如果socket是阻塞的,那么将会在当前或者下一次write/read系统调用的时候返回给应用层相应的错误。如果是nonblock,那么会在select/epoll中触发出对应的事件通知应用层去处理。如果fin包没发送到对端,那么在下一次write/read的时候内核会发送reset包作为回应。

nonblock

设置为nonblock=true后,由于read/write都是立刻返回,且通过select/epoll等处理重传超时/probe超时/keep alive超时/socket close等事件,所以根据应用层代码决定其超时特性。定时器超时事件发生的时间如上面几小节所述,和是否nonblock无关。nonblock的编程模式可以让应用层对这些事件做出响应。

7、总结


网络编程中超时时间是个重要但又容易被忽略的问题,这个问题只有在遇到物理机宕机,偶尔的网络抖动等平时遇不到的现象时候才会凸显。希望本篇文章可以对读者在以后遇到类似超时问题时有所帮助,只要涉及到阻塞式的网络请求,就一定要加超时,否则你将进入自己曾经种下的苦果,加班,掉的不是汗水,是头发。

原文https://zhuanlan.zhihu.com/p/535405145

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓文章来源地址https://www.toymoban.com/news/detail-663138.html

到了这里,关于深入剖析阻塞式socket的timeout的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入探究音视频开源库 WebRTC 中 NetEQ 音频抗网络延时与抗丢包的实现机制

    记录日常工作中遇到的音视频问题,以供大家借鉴或参考!

    2024年02月08日
    浏览(56)
  • 深入探究音视频开源库WebRTC中NetEQ音频抗网络延时与抗丢包的实现机制

    目录 1、引言 2、WebRTC简介 3、什么是NetEQ? 4、NetEQ技术详解 4.1、NetEQ概述 4.2、抖动消除技术 4.3、丢包补偿技术 4.4、NetEQ概要设计 4.5、NetEQ的命令机制 4.6、NetEQ的播放机制 4.7、MCU的控制机制 4.8、DSP的算法处理 4.9、DSP算法的模拟测试 5、NetEQ源文件说明 6、参考文档 C++软件异常

    2024年04月25日
    浏览(38)
  • socket 的阻塞模式和非阻塞模式

    对 socket 在阻塞和非阻塞模式下的各个函数的行为差别深入的理解是掌握网络编程的基本要求之一,是重点也是难点。 阻塞和非阻塞模式下,我们常讨论的具有不同行为表现的 socket 函数一般有如下几个,见下表: connect accept send (Linux 平台上对 socket 进行操作时也包括 write 函

    2024年02月04日
    浏览(30)
  • 音视频学习-音视频基础

    ` ◼ 像素:像素是一个图片的基本单位,pix是英语单词picture的简写,加上英语单词“元素element”,就得到了“pixel”,简称px,所以“像素”有“图像元素”之意。 ◼ 分辨率:是指图像的大小或尺寸。比如1920x1080。 ◼ 位深:是指在记录数字图像的颜色时,计算机实际上是用

    2024年02月12日
    浏览(41)
  • 【音视频】ffplay解析-音视频同步

    主要解析:以音频为基准,让视频同步音频 思路 视频慢了则丢掉部分视频帧(视觉-画⾯跳帧) 视频快了则继续渲染上⼀帧 具体实现 ffplay具体源码

    2024年02月07日
    浏览(41)
  • ffmpeg@音视频工具@音视频合并

    FFmpeg中文网 (github.net.cn) FFmpeg 是一款强大的开源跨平台音视频处理工具集,它包含了一系列命令行工具以及用于音频和视频编码解码、格式转换、抓取、流化等功能的库。FFmpeg 支持多种视频、音频格式和编解码器,能够进行音视频的压缩、封装、转码、分割、合并、过滤、抓

    2024年03月17日
    浏览(54)
  • 音视频 FFmpeg音视频处理流程

    推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家: 零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核) https://xxetb.xet.tech/s/VsFMs

    2024年02月12日
    浏览(44)
  • 音视频学习—音视频理论基础(2)

      音频是一种模拟信号,它是由声波转化而来的电信号。声波是通过气体、液体或固体传播的机械波,代表了声音的震动。在录制过程中,声音被转换成电信号,然后被储存在数字格式中。   声音的三要素是频率、振幅和波形   频率是指声波的振动次数,通常使用赫

    2024年04月29日
    浏览(26)
  • 音视频 ffmpeg命令提取音视频数据

    保留封装格式 提取视频 提取音频 推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家: 零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核) https://xxetb.xet.tech/s/VsFMs

    2024年02月10日
    浏览(45)
  • linux 音视频架构 linux音视频开发

    转载 mob6454cc65110a 2023-07-26 22:27:01 文章标签 linux 音视频架构 ci QT 视频教程 文章分类 架构 后端开发 阅读数 117 目录 前言 1、软件工具准备 a. 录音软件 b. 录屏软件 c. 摄像头软件 d. 安卓屏幕操作软件 e. 视频剪辑软件

    2024年02月07日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包