首页 » 网站推广 » php的timeout技巧_深入剖析壅塞式socket的timeout

php的timeout技巧_深入剖析壅塞式socket的timeout

访客 2024-12-11 0

扫一扫用手机浏览

文章目录 [+]

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

大略分类的话,可以将超时处理分成两类:

php的timeout技巧_深入剖析壅塞式socket的timeout

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

php的timeout技巧_深入剖析壅塞式socket的timeout
(图片来自网络侵删)
连接超时

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

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

内核层的超时剖析

我们都知道一个连接的建立须要经由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; dateSat Apr 2 14:27:33 CST 2022Trying 10.16.15.15...telnet: Unable to connect to remote host: Connection timed outSat Apr 2 14:29:40 CST 2022

综合剖析

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

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

connect系统调用

我们不雅观察一下此系统调用的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);

不设置SO_SNDTIMEO

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

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

cat /proc/sys/net/ipv4/tcp_syn_retries

对付系统调用,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)(1HZ)) / 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)(3HZ)) / 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设置。

干系视频推举

看完《tcp/ip详解》不能coding的,一次课开启设计tcp/ip协议栈

网络事理tcp/udp,网络编程epoll/reactor,口试中正经“八股文”

学习地址:C/C++Linux做事器开拓/后台架构师【零声教诲】-学习视频教程-腾讯教室

须要C/C++ Linux做事器架构师学习资料加qun812855908(资料包括C/C++,Linux,golang技能,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg,大厂口试题 等)

发送超时

在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的动态打算有关。
但是可以大致认为是同等的。

上述逻辑如下图所示:

write_timeout表格

tcp_retries2

buffer未满

buffer满

5

立即返回

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

15

立即返回

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

吸收超时

在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); ......}

上面的逻辑如下图所示:

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

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

很多运用便是基于这个无限超时来设计的,例如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

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

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

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

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

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

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

cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 即两个小时后开始探测cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 即每次探测间隔为75scat /proc/sys/net/ipv4/tcp_keepalve_probes 9 即一共探测9次

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

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

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

进程宕机后的超时

物理机溘然宕机和进程宕掉的表现不一样。
一个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的编程模式可以让运用层对这些事宜做出相应。

总结

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

标签:

相关文章

webphp术语技巧_解析计算机黑客术语

3.网页木马:表面上伪装成普通的网页文件或是将而已的代码直接插入到正常的网页文件中,当有人访问时,网页木马就会利用对方系统或者浏览...

网站推广 2024-12-13 阅读0 评论0

php对象置空技巧_PHP 面向对象

在现实天下里我们所面对的事情都是工具,如打算机、电视机、自行车等。工具的紧张三个特性:工具的行为:可以对 工具施加那些操作,开灯,...

网站推广 2024-12-13 阅读0 评论0