tcp socket的發送與接收緩衝區 .

來源:互聯網
上載者:User

http://blog.csdn.net/cyblueboy83/article/details/1791713

 

(1)應用程式可通過調用send(write, sendmsg等)利用tcp socket向網路發送應用資料,而tcp/ip協議棧再通過網路裝置介面把已經組織成struct sk_buff的應用資料(tcp資料報)真正發送到網路上,由於應用程式調用send的速度跟網路介質發送資料的速度存在差異,所以,一部分應用資料被組織成tcp資料報之後,會緩衝在tcp socket的發送緩衝隊列中,等待網路空閑時再發送出去。同時,tcp協議要求對端在收到tcp資料報後,要對其序號進行ACK,只有當收到一個tcp
資料報的ACK之後,才可以把這個tcp資料報(以一個struct sk_buff的形式存在)從socket的發送緩衝隊列中清除。
    tcp socket的發送緩衝區實際上是一個結構體struct sk_buff的隊列,我們可以把它稱為發送緩衝隊列,由結構體struct sock的成員sk_write_queue表示。sk_write_queue是一個結構體struct sk_buff_head類型,這是一個struct sk_buff的雙向鏈表,其定義如下:
    struct sk_buff_head {
        struct sk_buff  *next;      //後指標
        struct sk_buff  *prev;      //前指標
        __u32           qlen;       //隊列長度(即含有幾個struct sk_buff)
        spinlock_t      lock;       //鏈表鎖
    };
    核心代碼中,先在這個隊列中建立足夠存放資料的struct sk_buff,然後向隊列存入應用資料。
    結構體struct sock的成員sk_wmem_queued表示發送緩衝隊列中已指派的位元組數,一般來說,分配一個struct sk_buff是用於存放一個tcp資料報,其分配位元組數應該是MSS+協議首部長度。在我的實驗環境中,MSS值是1448,協議首部取最大長度 MAX_TCP_HEADER,在我的實驗環境中為224。經資料對齊處理後,最後struct sk_buff的truesize為1956。也就是隊列中每分配一個struct sk_buff,成員sk_wmem_queue的值就增加1956。
    struct sock的成員sk_forward_alloc是表示預分配長度。當我們第一次要為發送緩衝隊列分配一個struct sk_buff時,我們並不是直接分配需要的記憶體大小,而是會以記憶體頁為單位進行的預分配。
    tcp協議分配struct sk_buff的函數是sk_stream_alloc_pskb。它首先根據傳入的參數指定的大小在記憶體中分配一個struct sk_buff,如果成功,sk_forward_alloc取該大小值,並向上取整到頁(4096位元組)的整數倍。並累加到struct sock的成員sk_prot,也即表示tcp協議的結構體mytcp_prot的成員memory_allocated中,該成員是一個指標,指向變數 tcp_memory_allocated,它表示的是當前整個TCP協議當前為緩衝區所分配的記憶體(包括讀緩衝隊列)
    當把這個新分配成功的struct sk_buff放入到緩衝隊列sk_write_queue後,從sk_forward_alloc中減去該sk_buff的truesize值。第二次分配struct sk_buff時,只要再從sk_forward_alloc中減去新的sk_buff的truesize即可,如果sk_forward_alloc已經小於當前的truesize,則將其再加上一個頁的整數倍值,並累加入tcp_memory_allocated。
    也就是說,通過sk_forward_alloc使全域變數tcp_memory_allocated儲存當前tcp協議總的緩衝區分配記憶體的大小,並且該大小是頁邊界對齊的。

(2)前面講到struct sock的成員sk_forward_alloc表示預分配記憶體大小,用於向全域變數mytcp_memory_allocated累加當前已指派的整個 TCP協議的緩衝區大小。之所以要累加這個值,是為了對tcp協議總的可用緩衝區大小作限制。表示TCP協議的結構體mytcp_prot還有幾個成員與緩衝區相關。
    mysysctl_tcp_mem是一個數組,由mytcp_prot的成員sysctl_mem指向,數組共有三個元素,mysysctl_tcp_mem[0]表示對緩衝區總的可用大小的最低限制,當前總共分配的緩衝區大小低於這個值,則沒有問題,分配成功。 mysysctl_tcp_mem[2]表示對緩衝區可用大小的最高硬性限制,一旦總分配的緩衝區大小超出這個值,我們只好把tcp
socket 的發送緩衝區的預設大小sk_sndbuf減小為已指派緩衝隊列大小的一半,但不能小於SOCK_MIN_SNDBUF(2K),但保證這一次的分配成功。mysysctl_tcp_mem[1]介於前面兩個值的中間,這是一個警告值,一旦超出這個值,進入警告狀態,這個狀態下,根據調用參數來決定此次分配是否成功。
    這三個值的大小是根據所在系統的記憶體大小,在初始化時決定的,在我的實驗環境中,記憶體大小為256M,這三個值分配是:96K,128K,192K。它們可以通過/proc檔案系統,在/proc/sys/net/ipv4/tcp_mem中進行修改。當然,除非特別需要,一般無需改動這些預設值。
    mysysctl_tcp_wmem也是一個同樣結構的數組,表示發送緩衝區的大小限制,由mytcp_prot的成員sysctl_wmem指向,其預設值分別是4K,16K,128K。可以通過/proc檔案系統,在/proc/sys/net/ipv4/tcp_wmem中進行修改。struct sock的成員sk_sndbuf的值是真正的發送緩衝隊列的預設大小,其初始值取中間一個16K。在tcp資料報的發送過程中,一旦 sk_wmem_queued超過sk_sndbuf的值,則發送停止,等待發送緩衝區可用。因為有可能一批已發送出去的資料還沒有收到ACK,同時,緩衝隊列中的資料也可全部發出去,已達到清空緩衝隊列的目的,所以,只要在網路不是很差的情況下(差到沒有辦法收到ACK),這個等待在一段時間後會成功的。
    全域變數mytcp_memory_pressure是一個標誌,在tcp緩衝大小進入警告狀態時,它置1,否則置0。

(3)mytcp_sockets_allocated是到目前為止,整個tcp協議中建立的socket的個數,由mytcp_prot的成員 sockets_allocated指向。可以在/proc/net/sockstat檔案中查看,這隻是一個供統計查看用的資料,沒有任何實際的限制作用。
    mytcp_orphan_count表示整個tcp協議中待銷毀的socket的個數(已無用的socket),由mytcp_prot的成員orphan_count指向,也可以在/proc/net/sockstat檔案中查看。
    mysysctl_tcp_rmem是跟mysysctl_tcp_wmem相同結構的數組,表示接收緩衝區的大小限制,由mytcp_prot的成員 sysctl_rmem指向,其預設值分別是4096bytes,87380bytes,174760bytes。它們可以通過/proc檔案系統,在 /proc/sys/net/ipv4/tcp_rmem中進行修改。struct sock的成員sk_rcvbuf表示接收緩衝隊列的大小,其初始值取mysysctl_tcp_rmem[1],成員sk_receive_queue
是接收緩衝隊列,結構跟sk_write_queue相同。
    tcp socket的發送緩衝隊列跟接收緩衝隊列的大小既可以通過/proc檔案系統進行修改,也可以通過TCP選項操作進行修改。通訊端層級上的選項 SO_RCVBUF可用於擷取和修改接收緩衝隊列的大小(即strcut sock->sk_rcvbuf的值),比如下列的代碼可用於擷取當前系統的接收緩衝隊列大小:
    int rcvbuf_len;
    int len = sizeof(rcvbuf_len);
    if( getsockopt( fd, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf_len, &len ) < 0 ){
        perror("getsockopt: ");
        return -1;
    }
    printf("the recevice buf len: %d\n", rcvbuf_len );
    而通訊端層級上的選項SO_SNDBUF則用於擷取和修改發送緩衝隊列的大小(即struct sock->sk_sndbuf的值),代碼同上,只需改SO_RCVBUF為SO_SNDBUF即可。
    擷取發送和接收緩衝區的大小相對簡單一些,而設定的操作在核心中動作會稍微複雜一些,另外,在介面上也會有所差異,即由setsockopt傳入的表示緩衝區大小的參數是實際大小的1/2,即,如果想要設發送緩衝區的大小為20K,則需要這樣調用setsockopt:
     int rcvbuf_len = 10 * 1024;  //實際緩衝區大小的一半。
     int len = sizeof(rcvbuf_len);
     if( setsockopt( fd, SOL_SOCKET, SO_SNDBUF, (void *)&rcvbuf_len, len ) < 0 ){
        perror("getsockopt: ");
        return -1;
     }
    在核心中,首先核心要判斷新設定的值是否超過上限,若超過,則取上限為新值,發送和接收緩衝區大小的上限值分別為sysctl_wmem_max和 sysctl_rmem_max的2倍。這兩個全域變數的值是相等的,都為(sizeof(struct sk_buff) + 256) * 256,大概為64K負載資料,由於struct sk_buff的影響,實際發送和接收緩衝區的大小最大都可設到210K左右。它們的下限是2K,即緩衝區大小不能低於2K。
    另外,SO_SNDBUF和SO_RCVBUF有一個特殊的版本:SO_SNDBUFFORCE和SO_RCVBUFFORCE,它們不受發送和接收緩衝區大小上限的限制,可設定不小於2K的任意緩衝區大小。(完)

 

 

補充內容:

如果write的位元組數>socket發送緩衝區,tcp做何處理?
如果是非阻塞模式,是在設定的發送時間範圍內能發多少發多少.
在實際應用中,情況如下:
在非阻塞模式下,一般是用setsockopt函數設定發送阻塞的時間,然後調用send()發送資料,當超出這個時間,send函數會返回已發送的資料大小,
但是請注意此時緩衝中可能還有些資料沒有發送到網路上.
那麼當在應用程式層再一次調用send函數時,就會報告經典的錯誤:
Resource temporarily unavailable
那麼如果是阻塞情況,send函數會一直等到所有應用程式層的資料全部發送完畢再返回...

另外,如果是用UDP作為發送端,那麼都不需要考慮阻塞不阻塞的情況,會報告錯誤:
Message too long

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.