有了 TCP 協議本身的 ACK 機制為什么還需要業務層的ACK 機制?
答:這個問題從操作系統(linux/windows/android/ios)實現TCP協議的原理角度來說明更合適:
1 操作系統在TCP發送端創建了一個TCP發送緩沖區,在接收端創建了一個TCP接收緩沖區;
2 在發送端應用層程序調用send()方法成功后,實際是將數據寫入了TCP發送緩沖區;
3 根據TCP協議的規定,在TCP連接良好的情況下,TCP發送緩沖區的數據是“有序的可靠的”到達TCP接收緩沖區,然后回調接收方應用層程序來通知數據到達;
4 但是在TCP連接斷開的時候,在TCP的發送緩沖區和TCP的接收緩沖區中可能還有數據,那么操作系統如何處理呢?
首先,對於TCP發送緩沖區中還未發送的數據,操作系統不會通知應用層程序進行處理(試想一下:send()函數已經返回成功了,后面再告訴你失敗,這樣的系統如何設計?太復雜了...),通常的處理手段就是直接回收TCP發送緩存區及其socket資源;
對於TCP接收方來說,在還未監測到TCP連接斷開的時候,因為TCP接收緩沖區不再寫入數據了,所以會有足夠的時間進行處理,但若未來得及處理就發現了連接斷開,仍然會為了及時釋放資源,直接回收TCP接收緩存區和對應的socket資源。
總結一下就是: 發送方的應用層程序,調用send()方法返回成功的時候,數據實際是寫入到了TCP的發送緩沖區,而非已經被接收方的應用層程序處理。怎么辦呢?只能借助於應用層的ACK機制。
-----
tcp send函數的阻塞和非阻塞
tcp協議本身是可靠的,並不等於應用程序用tcp發送數據就一定是可靠的.不管是否阻塞,send發送的大小,並不代表對端recv到多少的數據.
在阻塞模式下, send函數的過程是將應用程序請求發送的數據拷貝到發送緩存中發送就返回.但由於發送緩存的存在,表現為:如果發送緩存大小比請求發送的大小要大,那么send函數立即返回,同時向網絡中發送數據;否則,send會等待接收端對之前發送數據的確認,以便騰出緩存空間容納新的待發送數據,再返回(接收端協議棧只要將數據收到接收緩存中,就會確認,並不一定要等待應用程序調用recv),如果一直沒有空間能容納待發送的數據,則一直阻塞;
在非阻塞模式下,send函數的過程僅僅是將數據拷貝到協議棧的緩存區而已,如果緩存區可用空間不夠,則盡能力的拷貝,立即返回成功拷貝的大小;如緩存區可用空間為0,則返回-1,同時設置errno為EAGAIN.
------
(一)基礎知識
- 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的目的是告訴對方其重組緩沖區大小的實際值,從而避免分片。
(二)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的發送緩沖隊列中清除。