1. Send 缓冲区满时阻塞代码:
Send底层调用函数tcp_sendmsg:
|
tcp_sendmsg: 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 new_segment: /* Allocate new segment. If the interface is SG, * allocate skb fitting to single page. */ if (!sk_stream_memory_free(sk)) goto wait_for_sndbuf;
。。。。。。。。。。。。。。。。。。。。。 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);
if ((err = sk_stream_wait_memory(sk, &timeo)) != 0) goto do_error;
mss_now = tcp_send_mss(sk, &size_goal, flags); } }
out: if (copied) tcp_push(sk, flags, mss_now, tp->nonagle); TCP_CHECK_TIMER(sk); release_sock(sk); return copied;
do_fault: if (!skb->len) { tcp_unlink_write_queue(skb, sk); /* It is the one place in all of TCP, except connection * reset, where we can be unlinking the send_head. */ tcp_check_send_head(sk, skb); sk_wmem_free_skb(sk, skb); }
do_error: if (copied) goto out; out_err: err = sk_stream_error(sk, flags, err); TCP_CHECK_TIMER(sk); release_sock(sk); return err; }
|
|
sk_stream_wait_memory: /** * sk_stream_wait_memory - Wait for more memory for a socket * @sk: socket to wait for memory * @timeo_p: for how long */ int sk_stream_wait_memory(struct sock *sk, long *timeo_p) { int err = 0; long vm_wait = 0; long current_timeo = *timeo_p; DEFINE_WAIT(wait);
if (sk_stream_memory_free(sk)) current_timeo = vm_wait = (net_random() % (HZ / 5)) + 2;
while (1) { set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) //如果调用close时,会唤醒调用send的任务,send会在此处返回 goto do_error; if (!*timeo_p) goto do_nonblock; if (signal_pending(current)) goto do_interrupted; clear_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags); if (sk_stream_memory_free(sk) && !vm_wait) break;
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); sk->sk_write_pending++; sk_wait_event(sk, ¤t_timeo, sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN) || (sk_stream_memory_free(sk) && !vm_wait)); sk->sk_write_pending--;
if (vm_wait) { vm_wait -= current_timeo; current_timeo = *timeo_p; if (current_timeo != MAX_SCHEDULE_TIMEOUT && (current_timeo -= vm_wait) < 0) current_timeo = 0; vm_wait = 0; } *timeo_p = current_timeo; } out: finish_wait(sk->sk_sleep, &wait); return err;
do_error: err = -EPIPE; goto out; do_nonblock: err = -EAGAIN; goto out; do_interrupted: err = sock_intr_errno(*timeo_p); goto out;
|
|
|
2. 链路出现故障时网络超时重传
2.1. Close 调用
Sock_close -> sock_release -> inet_release -> tcp_close
Tcp_close
|
void tcp_close(struct sock *sk, long timeout) 调用tcp_send_fin ,启动超时定时器
|
2.2. 超时定时器超时处理:
tcp_retransmit_timer -> tcp_retransmit_timer -> tcp_write_timeout
|
/* A write timeout has occurred. Process the after effects. */ static int tcp_write_timeout(struct sock *sk) { struct inet_connection_sock *icsk = inet_csk(sk); int retry_until; bool do_reset;
if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) { if (icsk->icsk_retransmits) dst_negative_advice(&sk->sk_dst_cache); retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries; } else { if (retransmits_timed_out(sk, sysctl_tcp_retries1)) { /* Black hole detection */ tcp_mtu_probing(icsk, sk);
dst_negative_advice(&sk->sk_dst_cache); }
retry_until = sysctl_tcp_retries2; if (sock_flag(sk, SOCK_DEAD)) { const int alive = (icsk->icsk_rto < TCP_RTO_MAX);
retry_until = tcp_orphan_retries(sk, alive); do_reset = alive || !retransmits_timed_out(sk, retry_until);
if (tcp_out_of_resources(sk, do_reset)) return 1; } }
if (retransmits_timed_out(sk, retry_until)) { /* Has it gone just too far? */ tcp_write_err(sk); //超时次数到 return 1; } return 0; }
|
tcp_write_err -》tcp_done
|
void tcp_done(struct sock *sk) { if (sk->sk_state == TCP_SYN_SENT || sk->sk_state == TCP_SYN_RECV) TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS);
tcp_set_state(sk, TCP_CLOSE); tcp_clear_xmit_timers(sk);
sk->sk_shutdown = SHUTDOWN_MASK;
if (!sock_flag(sk, SOCK_DEAD)) sk->sk_state_change(sk); //调用sock_def_wakeup唤醒此socket的任务,如send任务会被唤醒 else inet_csk_destroy_sock(sk); } |
调用close 没有马上关闭,且在客户端起来后一段时间自动关闭:
1. 网络出现问题,进入超时重传阶段,超时重传最大时间 一般对应13~30min
2.客户端起来后,服务器超时重传又发送了一次报文,超时时间到或者对端收到后发送reset (给已经重启的客户端发送报文,客户端回复reset 报文)
本文分析了当TCP Send缓冲区满时导致的阻塞情况,详细阐述了底层调用函数tcp_sendmsg的过程。同时,探讨了链路故障时的网络超时重传机制,包括Close调用的顺序以及超时定时器如何触发tcp_retransmit_timer,最终导致tcp_write_timeout和tcp_done。在网络问题导致超时重传阶段,该过程可能持续13至30分钟,如果客户端在此期间重启,服务器会再次发送报文,触发客户端回复reset报文。
1万+

被折叠的 条评论
为什么被折叠?



