tcp收發需要緩衝區,udp不需要緩衝區 詳解

2021-06-26 20:21:28 字數 4917 閱讀 3429

(一)基礎知識

(二)tcp與udp的輸出

每個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的雙向鍊錶,其定義如下:

struct sk_buff_head ;

(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 )

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 )

在核心中,首先核心要判斷新設定的值是否超過上限,若超過,則取上限為新值,傳送和接收緩衝區大小的上限值分別為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的任意緩衝區大小

tcp與udp緩衝區大小總結

1.tcp收發緩衝區預設值 root localhost cat proc sys net ipv4 tcp rmem 4096 87380 4194304 87380 tcp接收緩衝區的預設值 root localhost cat proc sys net ipv4 tcp wmem 4096 1...

關於tcp和udp的緩衝區

一 基礎知識 二 tcp與udp的輸出 每個tcp套介面有乙個傳送緩衝區,可以用so sndbuf套介面選項來改變這一緩衝區的大小。當應用程序呼叫write往套介面寫資料時,核心從應用程序緩衝區中拷貝所有資料到套介面的傳送緩衝區,如果套介面傳送緩衝區容不下應用程式的所有資料,或者是應用程序的緩衝區大...

UDP寫緩衝區(傳送緩衝區)分析

最近,碰到udp是否有寫緩衝區的疑問,對於應用,如下圖linux手冊中有設定udp傳送緩衝區相關屬性,也明確提到了send buffer的概念 那這是否意味著udp是有傳送緩衝區的嗎?我們再看一下 unix network programming 書中所述,這本書的作者權威性我就不多說了吧,在國內高...