zoukankan      html  css  js  c++  java
  • TCP层shutdown系统调用的实现分析

    概述

    shutdown系统调用在tcp层会调用两个函数,对于ESTABLISHED状态需要调用tcp_shutdown关闭连接,对于LISTEN和SYN_SENT状态则需要以非阻塞模式调用tcp_disconnect断开连接;本文除了对这两个函数进行分析以外,还会分析在shutdown关闭了读或者写之后,读写系统调用sendmsg和recvmsg将如何处理对应操作;

     1 /* 关闭操作 */
     2 int inet_shutdown(struct socket *sock, int how)
     3 {
     4         /*...*/
     5     switch (sk->sk_state) {
     6     case TCP_CLOSE:
     7         err = -ENOTCONN;
     8         /* Hack to wake up other listeners, who can poll for
     9            POLLHUP, even on eg. unconnected UDP sockets -- RR */
    10     default:
    11         /* 设置how值到sk_shutdown,并且调用传输层的shutdown */
    12         sk->sk_shutdown |= how;
    13         if (sk->sk_prot->shutdown)
    14             sk->sk_prot->shutdown(sk, how);
    15         break;
    16 
    17     /* Remaining two branches are temporary solution for missing
    18      * close() in multithreaded environment. It is _not_ a good idea,
    19      * but we have no choice until close() is repaired at VFS level.
    20      */
    21     case TCP_LISTEN:
    22         /* 监听状态,如果无接收方向的关闭操作,跳出 */
    23         if (!(how & RCV_SHUTDOWN))
    24             break;
    25         /* 有接收方向的关闭,继续 */
    26         /* Fall through */
    27     case TCP_SYN_SENT:
    28         /* 调用传输层的disconnect断开连接 */
    29         err = sk->sk_prot->disconnect(sk, O_NONBLOCK);
    30 
    31         /* 调增状态 */
    32         sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
    33         break;
    34     }
    35 
    36     /* Wake up anyone sleeping in poll. */
    37     /* 状态改变,唤醒等待的进程 */
    38     sk->sk_state_change(sk);
    39     release_sock(sk);
    40     return err;
    41 }
    tcp_shutdown

    tcp_shutdown函数完成设置关闭之后的状态,并且发送fin;注意只有接收端关闭时,不发送fin,只是在recvmsg系统调用中判断状态,不接收数据;

     1 /*
     2  *    Shutdown the sending side of a connection. Much like close except
     3  *    that we don't receive shut down or sock_set_flag(sk, SOCK_DEAD).
     4  */
     5 
     6 void tcp_shutdown(struct sock *sk, int how)
     7 {
     8     /*    We need to grab some memory, and put together a FIN,
     9      *    and then put it into the queue to be sent.
    10      *        Tim MacKenzie(tym@dibbler.cs.monash.edu.au) 4 Dec '92.
    11      */
    12     /* 不含有SEND_SHUTDOWN,返回,接收方关闭,不发fin */
    13     if (!(how & SEND_SHUTDOWN))
    14         return;
    15 
    16     /* If we've already sent a FIN, or it's a closed state, skip this. */
    17 
    18     /* 以下这几个状态发fin */
    19     if ((1 << sk->sk_state) &
    20         (TCPF_ESTABLISHED | TCPF_SYN_SENT |
    21          TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
    22         /* Clear out any half completed packets.  FIN if needed. */
    23         /* 设置新状态,发送fin */
    24         if (tcp_close_state(sk))
    25             tcp_send_fin(sk);
    26     }
    27 }

    tcp_close_state函数根据new_state状态表进行跳转,比如TCP_ESTABLISHED关闭时会跳转到TCP_FIN_WAIT1 | TCP_ACTION_FIN;

     1 static const unsigned char new_state[16] = {
     2   /* current state:        new state:      action:    */
     3   [0 /* (Invalid) */]    = TCP_CLOSE,
     4   [TCP_ESTABLISHED]    = TCP_FIN_WAIT1 | TCP_ACTION_FIN,
     5   [TCP_SYN_SENT]    = TCP_CLOSE,
     6   [TCP_SYN_RECV]    = TCP_FIN_WAIT1 | TCP_ACTION_FIN,
     7   [TCP_FIN_WAIT1]    = TCP_FIN_WAIT1,
     8   [TCP_FIN_WAIT2]    = TCP_FIN_WAIT2,
     9   [TCP_TIME_WAIT]    = TCP_CLOSE,
    10   [TCP_CLOSE]        = TCP_CLOSE,
    11   [TCP_CLOSE_WAIT]    = TCP_LAST_ACK  | TCP_ACTION_FIN,
    12   [TCP_LAST_ACK]    = TCP_LAST_ACK,
    13   [TCP_LISTEN]        = TCP_CLOSE,
    14   [TCP_CLOSING]        = TCP_CLOSING,
    15   [TCP_NEW_SYN_RECV]    = TCP_CLOSE,    /* should not happen ! */
    16 };
    17 
    18 static int tcp_close_state(struct sock *sk)
    19 {
    20     int next = (int)new_state[sk->sk_state];
    21     int ns = next & TCP_STATE_MASK;
    22 
    23     tcp_set_state(sk, ns);
    24 
    25     return next & TCP_ACTION_FIN;
    26 }

    tcp_send_fin完成fin的发送,如果队列中有数据段未发送,则共用最后一个数据段,在上面打fin标记,没有能重用的情况下,则新分配数据段;然后关闭nagle算法,并将队列中的数据段都发送出去;(注: 对于压力下,判断是否有数据这个逻辑未理解清楚)

     1 /* Send a FIN. The caller locks the socket for us.
     2  * We should try to send a FIN packet really hard, but eventually give up.
     3  */
     4 void tcp_send_fin(struct sock *sk)
     5 {
     6     struct sk_buff *skb, *tskb = tcp_write_queue_tail(sk);
     7     struct tcp_sock *tp = tcp_sk(sk);
     8 
     9     /* Optimization, tack on the FIN if we have one skb in write queue and
    10      * this skb was not yet sent, or we are under memory pressure.
    11      * Note: in the latter case, FIN packet will be sent after a timeout,
    12      * as TCP stack thinks it has already been transmitted.
    13      */
    14     /* 取到尾skb指针&& (有数据要发送 || 内存压力之下) */
    15     if (tskb && (tcp_send_head(sk) || tcp_under_memory_pressure(sk))) {
    16 coalesce:
    17         /* 尾skb上打fin标记 */
    18         TCP_SKB_CB(tskb)->tcp_flags |= TCPHDR_FIN;
    19         /* fin标记占用一个序号 */
    20         TCP_SKB_CB(tskb)->end_seq++;
    21         tp->write_seq++;
    22 
    23         /* tskb已经发送了,压力之下,认为已经发送了?? */
    24         if (!tcp_send_head(sk)) {
    25             /* This means tskb was already sent.
    26              * Pretend we included the FIN on previous transmit.
    27              * We need to set tp->snd_nxt to the value it would have
    28              * if FIN had been sent. This is because retransmit path
    29              * does not change tp->snd_nxt.
    30              */
    31             tp->snd_nxt++;
    32             return;
    33         }
    34     }
    35     /* 不满足上述情况,需要重新分配内存 */
    36     else {
    37         /* 分配skb */
    38         skb = alloc_skb_fclone(MAX_TCP_HEADER, sk->sk_allocation);
    39         if (unlikely(!skb)) {
    40             /* 队列为空无压力情况??  冲走一遍最后包共用fin流程*/
    41             if (tskb)
    42                 goto coalesce;
    43             return;
    44         }
    45 
    46         /* 初始化skb */
    47         skb_reserve(skb, MAX_TCP_HEADER);
    48         sk_forced_mem_schedule(sk, skb->truesize);
    49         /* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */
    50         tcp_init_nondata_skb(skb, tp->write_seq,
    51                      TCPHDR_ACK | TCPHDR_FIN);
    52 
    53         /* 添加到发送队列 */
    54         tcp_queue_skb(sk, skb);
    55     }
    56 
    57     /* 关闭nagle算法,将队列中的数据段全部发送出去 */
    58     __tcp_push_pending_frames(sk, tcp_current_mss(sk), TCP_NAGLE_OFF);
    59 }
    tcp_disconnect

    在连接为LISTEN或者SYN_SENT状态,会调用tcp_disconnect端口连接;函数首先对各种状态做分别的特有处理,然后再统一清理资源;

  • 相关阅读:
    ubuntu20.04.2设置配置静态ip方法
    如何在windows10上面运行HyperLedger Fabric1.4
    powershell中临时修改环境变量
    openssl查看pem格式证书细节
    git clone的时候遭遇fatal: early EOF fatal: index-pack failed解决办法
    修改sourcetree的推送账户
    ubuntu上kafka的配置与使用(二)--kafka和zookeeper集群的配置(kafka自带的zookeeper)
    ubuntu上kafka的配置与使用(一)--单机kafka的配置
    李航老师的《统计学习方法》第二章算法的matlab程序
    第8题——计算糖果
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/11751644.html
Copyright © 2011-2022 走看看