(轉)關於tcp和udp的緩衝區

(一)基礎知識數組

 

  • IPv4 數據報最大大小是65535(16位),包括IPv4頭部。
  • IPv6 數據報最大大小是65575,包括40個字節的IPv4頭部
  • MTU,這是由硬件規定的,如以太網的MTU是1500字節,IPv4要求最小MTU是68字節,IPv6要求最小MTU是576字節
  • path MTU: 指兩臺主機間的路徑上最小MTU
  • 分片(fragmentation):指ip數據報大小超過相應鏈路的MTU,IPv4和IPv6都將對ip數據進行分片,到達目的主機後進行重組。
  • IPv4頭部的DF位用於設置分片仍是不分片
  • MSS:最大分節大小,向對方TCP通告被通告方在每一個分節中能發送的最大TCP數據量。MSS的目的是告訴對方其重組緩衝區大小的實際值,從而避免分片。

 


(二)TCPUDP的輸出

每一個TCP套接口有一個發送緩衝區,能夠用SO_SNDBUF套接口選項來改變這一緩衝區的大小。當應用進程調用write往套接口寫數據時,內核從應用進程緩衝區中拷貝全部數據到套接口的發送緩衝區,若是套接口發送緩衝區容不下應用程序的全部數據,或者是應用進程的緩衝區大於套接口的發送緩衝區,或者是套接口的發送緩衝區中有別的數據,應用進程將被掛起。內核將不從write返回。直到應用進程緩衝區中的全部數據都拷貝到套接口發送緩衝區。因此,從寫一個TCP套接口的write調用成功返回僅僅表示咱們能夠從新使用應用進程緩衝區,它並非告訴咱們對方收到數據。TCP發給對方的數據,對方在收到數據時必須給矛確認,只有在收到對方的確認時,本方TCP纔會把TCP發送緩衝區中的數據刪除。

UDP由於是不可靠鏈接,沒必要保存應用進程的數據拷貝,應用進程中的數據在沿協議棧向下傳遞時,以某種形式拷貝到內核緩衝區,當數據鏈路層把數據傳出後就把內核緩衝區中數據拷貝刪除。所以它不須要一個發送緩衝區。寫UDP套接口的write返回表示應用程序的數據或數據分片已經進入鏈路層的輸出隊列,若是輸出隊列沒有足夠的空間存放數據,將返回錯誤ENOBUFS.緩存

 

 

 

(三)tcp socket的發送與接收緩衝區  網絡

 

  應用程序可經過調用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的雙向鏈表,其定義以下:socket


    struct sk_buff_head {
        struct sk_buff  *next;      //後指針
        struct sk_buff  *prev;      //前指針
        __u32             qlen;       //隊列長度(即含有幾個struct sk_buff)
        spinlock_t        lock;       //鏈表鎖
    };tcp


(1)函數

  內核代碼中,先在這個隊列中建立足夠存放數據的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的任意緩衝區大小

相關文章
相關標籤/搜索