<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          關(guān)于關(guān)閉一個(gè)還有沒發(fā)送數(shù)據(jù)完的TCP連接思考

          共 3979字,需瀏覽 8分鐘

           ·

          2020-11-28 02:46

          背景

          有一次,光神?在群問了個(gè)問題:

          當(dāng) close 一個(gè) TCP 連接時(shí),如果還有沒發(fā)送完的數(shù)據(jù)在緩沖區(qū)中,內(nèi)核會(huì)怎么處理?

          當(dāng)時(shí)我認(rèn)為,因?yàn)殛P(guān)閉 TCP 連接會(huì)觸發(fā)四次揮手過程,而為了讓四次揮手能夠快速完成,應(yīng)該會(huì)把發(fā)送緩沖區(qū)的數(shù)據(jù)清空,然后發(fā)送四次揮手的數(shù)據(jù)包。

          帶著疑問,我去查閱 Linux 源碼的實(shí)現(xiàn),下面就是關(guān)閉一個(gè) TCP 連接的過程。

          關(guān)閉 TCP 連接過程

          關(guān)閉一個(gè) TCP 連接可以使用?close()?系統(tǒng)調(diào)用,我們來分析一下當(dāng)調(diào)用?close()?關(guān)閉一個(gè) TCP 連接時(shí)會(huì)發(fā)生什么事情。

          當(dāng)調(diào)用?close()?系統(tǒng)調(diào)用時(shí),會(huì)觸發(fā)調(diào)用?sys_close()?內(nèi)核函數(shù),其實(shí)現(xiàn)如下:

          asmlinkage long sys_close(unsigned int fd){    struct file * filp;    struct files_struct *files = current->files;    ...    return filp_close(filp, files);    ...}

          sys_close()?函數(shù)最終會(huì)調(diào)用?file_close()?函數(shù)來關(guān)閉文件(由于在 Linux 中 socket 是一種特殊的文件),我們接著分析?filp_close()?函數(shù)的實(shí)現(xiàn):

          int filp_close(struct file *filp, fl_owner_t id){    ...    fput(filp);    return retval;}
          void fput(struct file * file){ ... if (atomic_dec_and_test(&file->f_count)) { ... if (file->f_op && file->f_op->release) file->f_op->release(inode, file); ... }}

          可以看到,最終會(huì)調(diào)用文件系統(tǒng)對(duì)應(yīng)的?release()?方法來處理關(guān)閉操作。對(duì)于 socket 文件系統(tǒng),release()?方法對(duì)應(yīng)的是?sock_close()?函數(shù),而?sock_close()?函數(shù)最終會(huì)調(diào)用?sock_release()?函數(shù),所以我們來看看?sock_release()?函數(shù)的實(shí)現(xiàn):

          void sock_release(struct socket *sock){    if (sock->ops)        sock->ops->release(sock);    ...}

          sock_release()?函數(shù)也很簡單,就是調(diào)用對(duì)應(yīng)?協(xié)議族?的?release()?方法,因?yàn)?Linux 的 socket 文件系統(tǒng)可以支持多種協(xié)議族,比如?INETUnix Domain SocketNetlink?等。而對(duì)應(yīng)?INET協(xié)議族(網(wǎng)絡(luò))?來說,這個(gè)?release()?方法對(duì)應(yīng)的是?inet_release()?函數(shù),inet_release()?函數(shù)實(shí)現(xiàn)如下:

          int inet_release(struct socket *sock){    struct sock *sk = sock->sk;
          if (sk) { long timeout; ... timeout = 0; if (sk->linger && !(current->flags & PF_EXITING)) timeout = sk->lingertime; sock->sk = NULL; sk->prot->close(sk, timeout); } return(0);}

          inet_release()?函數(shù)最終會(huì)調(diào)用對(duì)應(yīng)?傳輸層(TCP或者UDP)?的?close()?方法,對(duì)于?TCP協(xié)議?來說,close()?方法對(duì)應(yīng)的是?tcp_close()?函數(shù),tcp_close()?就是關(guān)閉 TCP 連接的最后站點(diǎn)。

          由于?tcp_close()?函數(shù)比較復(fù)雜,我們這里只分析當(dāng)發(fā)生緩沖區(qū)還有數(shù)據(jù)的情況下,內(nèi)核會(huì)怎么處理緩沖區(qū)的數(shù)據(jù)。

          void tcp_close(struct sock *sk, long timeout){    struct sk_buff *skb;    int data_was_unread = 0;
          ... // 如果接收緩沖區(qū)有數(shù)據(jù), 那么先情況接收緩沖區(qū)的數(shù)據(jù) while((skb= __skb_dequeue(&sk->receive_queue)) != NULL) { u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq - skb->h.th->fin; data_was_unread += len; __kfree_skb(skb); }
          ... if (data_was_unread != 0) { // 如果接收緩沖區(qū)有數(shù)據(jù)沒有處理 tcp_set_state(sk, TCP_CLOSE); // 把socket狀態(tài)設(shè)置為TCP_CLOSE tcp_send_active_reset(sk, GFP_KERNEL); // 發(fā)送一個(gè)reset包給對(duì)端連接 } else if (sk->linger && sk->lingertime==0) { ... } else if (tcp_close_state(sk)) { tcp_send_fin(sk); // 開始發(fā)生四次揮手包 } ...}

          從?tcp_close()?函數(shù)的實(shí)現(xiàn)可以看出,關(guān)閉過程主要有兩種情況:

          • 如果接收緩沖區(qū)還有數(shù)據(jù)沒有被用戶處理,那么就先把接收緩沖區(qū)的數(shù)據(jù)清空,并且發(fā)送一個(gè) reset 包給對(duì)端連接。

          • 如果接收緩沖區(qū)沒有數(shù)據(jù),那么就調(diào)用?tcp_send_fin()?函數(shù)開始進(jìn)行四次揮手過程。

          四次揮手過程如下圖:

          接下來,我們分析?tcp_send_fin()?函數(shù)的實(shí)現(xiàn):

          void tcp_send_fin(struct sock *sk){    struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);    struct sk_buff *skb = skb_peek_tail(&sk->write_queue); // 發(fā)送緩沖區(qū)列表最后一個(gè)緩沖塊    unsigned int mss_now;    ...    if (tp->send_head != NULL) {                         // 如果發(fā)送緩沖區(qū)不為空        TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;        // 把最后一個(gè)發(fā)送緩沖塊設(shè)置FIN標(biāo)志        TCP_SKB_CB(skb)->end_seq++;        tp->write_seq++;    } else {                                             // 如果發(fā)送緩沖區(qū)為空        for (;;) {            skb = alloc_skb(MAX_TCP_HEADER, GFP_KERNEL); // 申請(qǐng)一個(gè)新的緩沖塊            if (skb)                break;            current->policy |= SCHED_YIELD;            schedule();        }
          skb_reserve(skb, MAX_TCP_HEADER); skb->csum = 0; TCP_SKB_CB(skb)->flags = (TCPCB_FLAG_ACK | TCPCB_FLAG_FIN); // 設(shè)置FIN標(biāo)志 TCP_SKB_CB(skb)->sacked = 0;
          TCP_SKB_CB(skb)->seq = tp->write_seq; TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq + 1; tcp_send_skb(sk, skb, 1, mss_now); // 發(fā)送給對(duì)端連接 } ...}

          在?tcp_send_fin()?函數(shù)我們終于找到了當(dāng)發(fā)送緩沖區(qū)不為空的處理,當(dāng)發(fā)送緩沖區(qū)不為空時(shí),首先會(huì)獲取發(fā)送緩沖區(qū)的最后一個(gè)緩沖塊,然后把這個(gè)緩沖區(qū)的?FIN標(biāo)志位?設(shè)置上。

          所以我前面的想法是錯(cuò)的,當(dāng)關(guān)閉一個(gè) TCP 連接時(shí),如果發(fā)送緩沖區(qū)還有數(shù)據(jù)沒發(fā)送完,那么內(nèi)核只會(huì)把發(fā)送緩沖區(qū)最后一個(gè)緩沖塊設(shè)置上?FIN標(biāo)志,而不是把發(fā)送緩沖區(qū)清空。


          瀏覽 29
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩欧美三级在线 | 大鸡巴操逼| 少妇大战黑人无套A片 | 丁香五月婷婷综合 | 丁香六月婷婷综合激情欧美 |