函數tcp_output負責發送報文段,代碼中很多地方都調用了它。
tcp_usrreq在多種請求處理中調用了這一函數:處理PRU_CONNECT,發送初始SYN;處理PRU_SHUTDOWN,發送FIN;
處理PRU_RCVD,應用進程從插口接收緩存中讀取若干數據後可能需要發送新的窗口大小通告;處理PRU_SEND,發送
數據;處理PRU_SENDOOB,發送帶外數據。
tcp_fasttimo調用它發送延遲的ACK。
tcp_timers在重傳定時器超時時,調用發送窗口探測報文段。
tcp_timers在持續定時器超時時,調用它發送窗口探測報文段。
tcp_drop調用它發送RST。
tcp_disconect調用它發送FIN。
tcp_input在需要輸出或需要立即發送ACK時調用它。
tcp_input在收到一個純ACK報文段且本地有數據發送時調用它。
tcp_input在連續收到3個重複的ACK時,調用它發送一個單一報文段。(快速重傳算法)
tcp_output首先確定是否有報文段等待發送,除了存在需要發往連接對端的數據外,tcp輸出還受到其他許多因素的控制。
例如,對端可能通告接收窗口爲0,阻止TCP發送任何數據;Nagle算法阻止TCP發送大量小報文段;慢啓動和避免擁塞
算法限制TCP發送的數據量。相反,有些函數置位一些特殊標誌,強迫tcp_output發送報文段,如TF_ACKNOW標誌置位
意味着必須發送一個ACK。如果tcp_output確定不發送某個報文段,數據將保留在插口的發送緩存中,等待下一次調用
該函數。
1.tcp_output概述
函數的大概處理流程如下:
1.是否等待對端的ACK?如果發送的最大序號等於最早未確認過的序號,即不等待對端發送ACK。
2.返回慢啓動。如果TCP不等待對端發送ACK,而且在一個往返時間內沒有收到對端發送的其他報文段,設置擁塞窗口
爲僅能容納一個報文段,從而在發送下一個報文段時,強迫執行慢啓動算法。如果數據傳輸中出現了顯著的停頓,說明
與先前測量RTT時相比,網絡條件已經發生了變化,Net/3假定出現了最壞情況,因而返回慢啓動狀態。
3.發送多個報文段,調用ip_output發送一個報文段。但如果ip_output確實有多個報文段需要發送,函數將試圖發送另
一個報文段。因此,ip_output的一次調用能夠發送多個報文段。
2.決定是否發送一個報文段
某些情況下,在報文段準備好之前已經調用了tcp_output,例如,當插口層從插口的接收緩存中移走數據,傳遞給用戶進程
時,會生成PRU_RCVD請求。儘管不一定,但完全有可能因爲進程取走了大量數據,而使得TCP有必要發送新的窗口通告。
tcp_output的前半部分確定是否存在需要發往對端的報文段。如果沒有,則函數返回,不執行發送操作。
3.TCP選項
TCP首部可以有任選項。下圖列出了Net/3支持的選項格式。
所有選項以1字節的kind字段開頭,確定選項類型。頭兩個選項(kind=0或者kind=1)只有1個字節。其餘3個選項都是多字節
的,帶有len字段,位於kind字段之後,存儲選項的長度。長度中包含kind字段和len字段。
4.窗口大小選項
窗口大小選項,避免了TCP首部窗口大小字段只有16bit的限制。如果網絡帶寬較高或延時較長,在需要較大的窗口,稱爲
長肥管道。現代網絡需要較大的窗口,以獲取最大的TCP吞吐量。
上圖中的偏移量最小值爲0,最大值爲14,即窗口最大可設定爲65536*2^14字節。
窗口大小選項智能出現在SYN中,因此,連接建立後,每個傳輸方向上的縮放因子時固定不變的。
TCP控制塊中的兩個變量snd_scale和rcv_scale,分別規定了發送窗口和接收窗口的偏移量。
TCP發送SYN時,無論是主動打開或被動打開,都是根據本地插口接收緩存代銷選取rcv_scale值,填充窗口大小選項的偏移
量字段。
5.時間戳選項
發送方在每個報文中放入時間戳,接收方在ACK中將時間戳發回。對於每個收到的ACK,發送方根據返回的時間戳計算相應
的RTT樣本值。
6.發送一個報文段
tcp_output接下來的代碼負責發送報文段----填充TCP報文首部的所有字段,並傳遞給IP層準備發送。大概的處理流程如下:
1.構造MSS選項。
2.是否發送窗口大小選項。
3.如果需要發送窗口大小選項,則構造窗口大小選項。
4.是否需要發送時間戳。
5.如果需要發送時間戳,則構造時間戳選項。
6.選項加入後是否會造成報文長度越界。
7.更新統計值。
8.爲IP和TCP首部分配mbuf。
9.向mbuf中複製數據。
10.置位PSH標誌。
11.更新統計值。
12.得到存儲IP和TCP首部的mbuf。
13.向mbuf中複製IP和TCP首部模板。
14.如果FIN將重傳,遞減snd_nxt。
15.設置報文的序號字段。
16.設置報文的確認字段。
17.如果存在首部選項,設置首部長度字段。
18.通告的窗口大小應大於最大報文段長度。
19.遵循連接的通告窗口大小的上限。
20.不要縮小窗口。
21.設置緊急數據偏移量。
22.保存起始序號。
23.增加snd_nxt。
24.更新snd_max.
25.設定重傳定時器。
26.持續狀態。
27.爲插口調試添加路由記錄。
28.設置IP長度、TTL和TOS。
29.向IP傳遞數據報。
30.更新rcv_adv和last_ack_sent。
31.是否還有數據需要發送。