mature是什么意思ure的用法读音典-定语从句例句


2023年4月19日发(作者:请你支持我口白的拼音 语交际)从linux源码看socket(tcp)的timeout
⽹络编程中超时时间是⼀个重要但⼜容易被忽略的问题,对其的设置需要仔细斟酌。在经历了数次物理机宕机之后,笔者详细的考察了在⽹络编程
(tcp)中的各种超时设置,于是就有了本篇博⽂。本⽂⼤部分讨论的是socket设置为block的情况,即setNonblock(false),仅在最后提及了
nonblock socket(本⽂基于linux 2.6.32-431内核)。
connectTimeout
在讨论connectTimeout之前,让我们先看下java和C语⾔对于socket connect调⽤的函数签名:
java:
// 函数调⽤中携带有超时时间
public void connect(SocketAddress endpoint, int timeout) ;
C语⾔:
// 函数调⽤中并不携带超时时间
int connect(int sockfd, const struct sockaddr * sockaddr, socklen_t socklent)
操作系统提供的connect系统调⽤并没有提供timeout的参数设置⽽java却有,我们先考察⼀下原⽣系统调⽤的超时策略。
connect系统调⽤
我们观察⼀下此系统调⽤的kernel源码,调⽤栈如下所⽰:
connect[⽤户态]
|->SYSCALL_DEFINE3(connect)[内核态]
|->sock->ops->connect
由于我们考察的是tcp的connect,其socket的内部结构如下图所⽰:

最终调⽤的是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 ans空山不见人的下一句 wer. */
// 由于是刚建⽴连接,所以其rtoTCP_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。我们继续考察:
int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
int addr_len, int flags)
{
......
// tcp_v4_connect=>tcp_connect
err = sk->sk_prot->connect(sk, uaddr, addr_len);
// 这边⽤的是sk->sk_sndtimeo
timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
......
inet_wait_for_connect(sk, timeo));
......
out:
release_sock(sk);
return err;
sock_error:
err = sock_error(sk) ? : -ECONNABORTED;
sock->state = SS_UNCONNECTED;
if (sk->sk_prot->disconnect(sk, flags))
sock->state = SS_DISCONNECTING;
goto out
}
由上⾯代码可见,可以采⽤设置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 笔者机器上是5
来决定。那么我们就来看⼀下这个重传到底是多长时间:
tcp_connect:
// 设置的初始超时时间为icsk_rto=TCP_TIMEOUT_INIT1s
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
其重传定时器的回掉函数为tcp_retransmit_timer:
void tcp_retransmit_timer(struct sock *sk)
{
......
// 检测是否超时
if (tcp_write_timeout(sk))
goto out;
......
// icsk_rto = icsk_rto * 2,由于syn阶段,所以isck_rto不会由于⽹络传输⽽改变做最好的自己
// 重传的时候何处春江无月明上一句 会以1,2,4,8指数递增
icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);
// 重设timer
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX);
out:;
}
只不过在connect时刻,重传的计算以TCP_TIMEOUT_INIT为单位进⾏计算。⽽ESTABLISHED(read/write)时刻,重传以TCP_RTO_MIN进
⾏计算。那么根据这段重传逻辑,我们就可以计算出不同tcp_syn_retries最终表现的超时时间。如下图所⽰:


那么整理下表格,对于系统调⽤,connect的超时时间为:

另外,tcp_syn_retries重传次数可以在单个socket中通过setsockopt设置。
JAVA connect API
现在我们考察下java的connect api,其connect最终调⽤下⾯的代码:
Java_java_net_PlainSocketImpl_socketConnect(...){
if (timeout <= 0) {
......
connect_rv = NET_Connect(fd, (struct sockaddr *)&him, len);
.....
}else{
// 如果timeout > 0 ,则设置为nonblock模式
SET_NONBLOCKING(fd);
/* no need to use NET_Connect as non-blocking */
connect_rv = connect(fd, (struct sockaddr *)&him, len);
/*
* 这边⽤系统调⽤select来模拟阻塞调⽤超时
*/
while (1) {
......
struct timeval t;
_sec = timeout / 1000;
_usec = (timeout % 1000) * 1000;
connect_rv = NET_Select(fd+1, 0, &wr, &ex, &t);
......
}
......
// 重新设置为阻塞模式
SET_BLOCKING(fd);
......
}
}
其和connect系统调⽤的不同点是,在timeout为0的时候,⾛默认的系统调⽤不设置超时时间的逻辑。在timeout>0时,将socket设置为⾮阻
塞,然后⽤select系统调⽤去模拟超时,⽽没有⾛linux本⾝的超时逻辑,


由于没有java并没有设置so_sndtimeo的选项,所以在timeout为0的时候,直接就通过重传次数来控制超时时间。⽽在调⽤connect时设置了
timeout(不为0)的时候,超时时间如下表格所⽰:

socketTimeout
write系统调⽤的超时时间
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 笔者机器上是5,默认是15
SO_SNDTIMEO不设置,write buffer满之后对端不消费,导致buffer⼀直满的情况
和上⾯ack超时有些许不⼀样的是,⼀个逻辑是⽤TCP_RTO_MIN通过tcp_retries2计算出来的时间。另⼀个是真的通过重传超过tcp_retries2次
数来time_out,两者的区别和rto的动态计算有关。但是可以⼤致认为是⼀致的。
上述逻辑如下图所⽰:

write_timeout表格
[图⽚上传失败...(image-d34447-88)]
java的SocketOutputStream的som的sockWrite0超时时间Write0超时时间
java的sockWrite0没有设置超时时间的地⽅,同时也没有设置过SO_SNDTIMEOUT,其直接调⽤了系统调⽤,所以其超时时间和write系统调⽤
保持⼀致。
readTimdTimeoeout
ReadTimeout可能是最容易导致问题的地⽅。我们先看下系统调⽤的源码:
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的消费者逻辑。
java的SocketInputStreamm的so的sockRead0超时时间

java的超时时间由SO_TIMOUT决定,⽽linux的socket并没有这个选项。其sockRead0和上⾯的java connect⼀样,在SO_TIMEOUT>0的时
候依旧是由nonblock socket模拟,在此就不再赘述了。

对端物理机宕机之后的tim对端物理机宕机之后的timeoeout
对端物理机宕机后还依旧有数据发送
对端物理机宕机时对端内核也gg了(不会发出任何包通知宕机),那么本端发送任何数据给对端都不会有响应。其超时时间就由上⾯讨论的 min(设
置的socket超时[例如SO_TIMEOUT],内核内部的定时器超时来决定)。
对端物理机李白写敬亭山的5首诗 宕机后没有数据发送,但在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 即每次探测间隔为75s
cat /proc/sys/n听筝翻译 et/ipv4/tcp_keepalve_probes 9 即⼀共探测9
可以在setsockops中对单独的socket指定是否启⽤keepalive定时器(java也可以)。
对端物理机宕机后没有数据发送,也没有read等待
和上⾯同理,也是在keepalive定时器超时之后,将连接close。所以我们可以看到⼀个不活跃的socket在对端物理机突然宕机之后,依旧是
ESTABLISHED状态,过很长⼀段时间之后才会关闭。
进程宕后的超时
如果仅仅是对端进程宕机的话(进程所在内核会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的编程模式可以让
应⽤层对这些事件做出响应。
总结
⽹络编程中超时时间是个重要但⼜容易被忽略的问题,这个问题只有在遇到物理机宕机等平时遇不到的现象时候才会凸显。笔者在经历数次物理机
宕机之后才好好的研究了⼀番,希望本篇⽂章可以对读者在以后遇到类似超时问题时有所帮助。

甜食;一点冰淇淋和饼的英文翻译么说-foul


更多推荐

blockout是什么意思ckout在线翻译读音