linux核心協議棧 TCP資料傳送之傳送視窗

2021-10-24 17:05:02 字數 3959 閱讀 8218

目錄

1 傳送視窗概述

2 snd_una 和 snd_wnd 的更新

2.1 傳送視窗初始化

2.1.1 客戶端初始化

2.1.2 伺服器端初始化

2.2 本地接收視窗 rcv_wnd 通告

2.2.1 客戶端傳送

2.2.2 伺服器傳送

2.3 傳輸過程中更新傳送視窗

2.3.1 傳送視窗更新條件

3 傳送視窗對傳送過程的影響

tcp的傳送過程由滑動視窗控制,而滑動視窗的大小受限於傳送視窗和擁塞視窗,擁塞視窗由擁塞控制演算法的代表,而傳送視窗是流量控制演算法的代表,這篇筆記記錄了傳送視窗相關的內容,包括傳送視窗的初始化、更新、以及它是如何影響資料傳送過程的。

tcp的傳送視窗可以用下圖表示:

如圖所示,tcb中有三個成員和傳送視窗強相關。

struct tcp_sock ;
注意:傳送視窗的大小描述的是對端接受緩衝區的大小,即對端的的接受視窗的大小

snd_una是傳送視窗的左邊界,如果該欄位更新,即使傳送視窗大小snd_wnd沒有發生變化,整個傳送視窗也會前移,這樣從流量控制的角度,就可以傳送更多的資料(是否真的可以傳送,還要考慮擁塞視窗等其它因素)。

可以想的到,snd_una的初始化一定發生在第乙個資料段傳送過程中,而snd_wnd的初始化應該是發生在第乙個輸入段處理過程中,所以需要客戶端和伺服器端分開來看。

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)

static void tcp_connect_init(struct sock *sk)

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,

struct tcphdr *th, unsigned len)

}

正面理解的話,伺服器端對snd_una的初始化應該是發生在傳送syn+ack段時,但是實際上不是,而是發生在收到第三次握手的ack段時。如筆記tcp之伺服器端收到ack包所述,三次握手完成後,建立了子套接字,然後在tcp_child_process()中會繼續呼叫tcp_rcv_state_process()處理ack報文,**如下:

int tcp_child_process(struct sock *parent, struct sock *child,

struct sk_buff *skb)

else

bh_unlock_sock(child);

sock_put(child);

return ret;

}int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,

struct tcphdr *th, unsigned len)

break;

... }//end of switch()

} else

goto discard;

... return 0;

}

上述的初始化過程已經表述客戶端和伺服器的本地傳送視窗snd_wnd 均是在就收到對端的報文解析tcp的滑動視窗字段:ntohs(th->window)而賦值的。那麼window是如何傳送的了?

tcp_connect

--tcp_connect_init

--tcp_select_initial_window //根據本地的接受緩衝區,mtu計算得出本地的接受視窗

--tcp_transmit_skb //傳送window

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,

gfp_t gfp_mask)

else

...}

tcp_v4_send_synack

--__tcp_v4_send_synack

--tcp_make_synack

--tcp_select_initial_window

--ip_build_and_send_pkt

struct sk_buff *tcp_make_synack(struct sock *sk, struct dst_entry *dst,

struct request_sock *req)

...th = tcp_hdr(skb);

memset(th, 0, sizeof(struct tcphdr));

th->syn = 1;

th->ack = 1;

.../* rfc1323: the window in syn & syn/ack segments is never scaled. */

th->window = htons(min(req->rcv_wnd, 65535u));

...return skb;

}

顯然,資料傳輸過程中,應該在收到ack後更新snd_una和snd_wnd。如果輸入段中攜帶了ack,最終都會有tcp_ack()處理確認相關的內容。

快速路徑處理情況,因為此時在接收資料,所以輸入段的windows欄位一定是沒有發生變化的,所以無需更新snd_wnd的值,直接更新snd_una即可。

慢速路徑處理情況,情況複雜,需要做更多的判斷,呼叫 tcp_ack_update_window() 完成傳送視窗的更新。

static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)

else

...}

/* update our send window.

* * window update algorithm, described in rfc793/rfc1122 (used in linux-2.2

* and in freebsd. netbsd's one is even worse.) is wrong.

*/static int tcp_ack_update_window(struct sock *sk, struct sk_buff *skb, u32 ack,

u32 ack_seq)

} }//更新傳送視窗左邊界

tp->snd_una = ack;

return flag;

}

慢速路徑下的核心是判斷什麼時候應該更新傳送視窗,這是由tcp_may_update_window()實現的。

/* check that window update is acceptable.

* the function assumes that snd_una<=ack<=snd_next.

*/static inline int tcp_may_update_window(const struct tcp_sock *tp,

const u32 ack, const u32 ack_seq,

const u32 nwin)

這裡要明白的是,傳送視窗是實現流量控制的關鍵,它影響的只有新資料的傳送過程,與重傳無關,因為重傳的資料一定是在對端接收能力之內。

從《linux核心協議棧 tcp層資料傳送之傳送新數》中有看到新資料傳送的兩個關鍵函式tcp_write_xmit()和tcp_push_one(),而且二者非常相似,參考之前的筆記中分析的tcp_snd_wnd_test()和tcp_mss_split_point()就可以明白傳送視窗是如何影響傳送過程的。

linux核心協議棧 鄰居協議概述

1 什麼是鄰居協議?ndp neighbor discovery protocol,鄰居發現協議 是ipv6的乙個關鍵協議,它組合了ipv4中的arp icmp路由器發現和icmp重定向等協議,並對它們作了改進。作為ipv6的基礎性協議,ndp還提供了字首發現 鄰居不可達檢測 重複位址監測 位址自動...

LINUX核心引數,針對TCP協議優化

程序可以同時開啟的最大控制代碼數,限制最大併發連線數 fs.file max 999999 允許time wait狀態的socket重新用於新的tcp連線 net.ipv4.tcp tw reuse 1 當keepalive啟用時,tcp傳送keepalive訊息的頻度,預設是2個小時。設定小些,可...

TCP IP協議棧 之 TCP協議

1 tcp的特點 2 tcp資料格式 tcp在ip協議的基礎上進行傳輸資料,tcp資料在ip報文中的位置如下 ip頭部 20位元組 tcp頭部 20字 tcp資料 tcp報文包含頭部和資料兩部分,其資料格式如下 源埠號 16位 目的埠號 16位 序列號 32位 確認號 32位 頭部長度 4位 保留 ...