當用TCP/IP協議進行通信時,在發送端,send首先會將數據copy到協議的緩存區,然后協議會將數據發送到接收端,接着會等待接收端協議收到數據的ack,如果沒有收到ack,協議就會重發數據,在這一過程中send一直在等待,直到收到ack,當協議收到ack后才將協議緩存中的數據刪除,因此從協議上來說是不會丟失數據的。
但是協議沒有丟失數據並不能保證接收端應用程序就一定會處理了數據,因此,在接收端的應用層增加ack是有必要的,因為有可能因為某些原因(比如:接收端機器配置低),應層沒來得及處理數據,這也是TCP協議應用層加ack的意義所在。
而同樣,對於在實際應用中設計應用層協議的時候,合適的ACK機制很重要。即使是基於TCP/IP的應用層,也要實現自己的ACK機制。因為TCP/IP的ACK是傳輸層的ACK,並不一定表示應用層已經處理了收到的消息,因為數據可能還在內核中沒有被應用層讀取。所以,應用層協議要有自己的ACK,進行應用層的消息確認。
因為很多時候消息由tcp層交給應用層之后還可能出現丟失的情況,比如客戶端落本地db失敗了,類似這種。
TCP屬於傳輸層,而IM服務屬於應用層,TCP的ACK只能保證傳輸層的可靠性,即A端到B端的可靠性,但是不能保證數據能夠被應用層正確可靠處理,比如應用層里面的業務邏輯導致消息處理失敗了,TCP層是不知道的。
Tcp的ack機制可以保證通過tcp傳輸的數據被對端內核接受並放入對應的socket接受緩存區里面,但是接下來進程讀取緩存區以及進行邏輯處理可能會出現問題,所以需要應用層的ack機制保證數據包被進程讀取並正確的處理。
每個TCP套接口有一個發送緩沖區,可以用SO_SNDBUF套接口選項來改變這一緩沖區的大小。當應用進程調用write往套接口寫數據時,內核從應用進程緩沖區中拷貝所有數據到套接口的發送緩沖區,如果套接口發送緩沖區容不下應用程序的所有數據,或者是應用進程的緩沖區大於套接口的發送緩沖區,或者是套接口的發送緩沖區中有別的數據,應用進程將被掛起。內核將不從write返回。直到應用進程緩沖區中的所有數據都拷貝到套接口發送緩沖區。所以,從寫一個TCP套接口的write調用成功返回僅僅表示我們可以重新使用應用進程緩沖區,它並不是告訴我們對方收到數據。TCP發給對方的數據,對方在收到數據時必須給矛確認,只有在收到對方的確認時,本方TCP才會把TCP發送緩沖區中的數據刪除。
-------
有了 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機制。