《TCP/IP詳解·卷一》看了三遍纔算整明白個大概,一直想作個總結。緩存
最初對TCP的印象很簡單:丟包重傳、流數據。丟包重傳很好理解,「流數據」是什麼鬼?服務器
知乎上看到個極好的解釋:把TCP看做用管子往對端灌水,水是數據,它們之間沒有邊界,且先發先到;UDP是往對端滾小球,它們之間有明確邊界,且可能每一個小球速度不一樣,先滾的不必定先到,得本身處理亂序。網絡
編碼上也可看出,TCP的send回調帶有dwNumberOfBytesTransferred參數,描述本次網絡IO發送了多少字節數據,而不是給它多少發多少。極可能讓發的500字節只發了300,剩200留着呢,業務層得自行處理。接收方也有個常規的粘包操做,把連成一片的網絡buffer解析爲單個消息包,再分發給各業務邏輯。socket
那就試着複述下這個管子灌水的過程吧。tcp
首先是管道的創建、拆除。即很是有名的TCP三次握手。ui
終止要通過四次握手。編碼
爲何會多一次呢?從圖中能夠看出,創建時報文段2包含了SYN、ACK,而終止時FIN與ACK是分兩條發的,why?spa
TCP鏈接是雙全工的,兩端都能收發,向對方交付FIN僅僅只是告知對端本身再也不發送數據(仍須能收,即TCP的半關閉),而對端什麼時候終止發送,是其業務邏輯決定的。終止時報文段二、報文段3的觸發,源自兩個不一樣層次的東西,沒法合併成一條(即便有ack延時確認)。設計
剩下的問題是,創建/終止的這些報文,任意一條都會丟失。其中最麻煩的是終止握手的最後一條ACK,它的重傳依賴對端FIN的超時,因此主動發起關閉的一方不能在收到最後一個FIN時,即刻關閉鏈接。3d
這就是很是著名的TIME_WAIT狀態。主動關閉方要等2×MSL(報文段最大生成時間)後方可真正關閉鏈接,留有足夠的時間應對最後ACK的丟失(對端重傳FIN)。
而後這個TIME_WAIT在使用上引發了些麻煩——此狀態的鏈接,不能再接收數據(報文段直接拋棄),也不能重用(舊鏈接的在路上的數據可能會被當成新鏈接的)。
在服務器端就要注意,避免短期內大量關閉socket,產生的衆多TIME_WAIT鏈接可能會耗盡系統資源,導致新鏈接沒法創建。
覺得這就完了嗎?
Quiet Time。
平靜時間:TCP在重啓後的MSL秒內不能創建任務鏈接。
如若沒有Quiet Time,TIME_WAIT的2×MSL出現故障怎麼辦?好比服務器中途宕機很快重啓,並按流程當即使用了以前一樣的一對socket。假設故障發生時那對舊socket剛好有報文段在網絡上,那重啓後的新鏈接就徹底沒法區分此條報文段了,會被看成正常數據接收。
在分析斷開爲什麼要四次時介紹了TCP半關閉,相應的也有TCP半打開。
好比客戶端突然斷電停機,因爲沒來得及跟服務器有交互,因此服務器是不知道對端已關閉的。只要不在此鏈接上傳送數據,就永遠不會知曉另外一方已然完蛋。
TCP四大定時器(重傳、堅持、保活、2MSL)之一的保活定時器要解決的問題。
一條鏈接在給定時間內沒任何交互,服務器會向客戶端發送探查報文,若超時後仍未收到迴應,或收到RST復位(客戶端已重啓),即終止該鏈接。
固然還有「同時打開/同時關閉」的問題。與上述常規打開/關閉相比,只是狀態遷移有所不一樣,對照示意圖一看即明。
這裏再介紹下鏈接創建過程當中的「呼入請求隊列」。
三次握手成功是個及時操做,系統不可能緊盯着它等處理。那服務器繁忙時,有鏈接到了怎麼辦?很簡單,排隊,等有空了挨個處理。
「呼入請求隊列」即是這個。須要注意的:客戶端connect成功,僅僅只表示被放入了svr的呼入請求隊列(此時client仍能發數據,收的數據被放在tcp緩存了),還未交接給應用層。svr端Accept時纔會將隊列中的鏈接取出,進而處理。
PS:若是不取或取慢了,client會因tcp發送緩衝滿而一直send失敗。因此怎麼調Accept也是要考慮設計的
PPS:「呼入請求隊列」有長度限制(積壓值 backlog),超事後再來的鏈接請求就一直失敗了。
業務層上,關閉比創建更難些,難點在於:發送數據的完整性(很可能你的用戶buffer/tcp緩衝中都殘留有正常的邏輯數據),以及關閉的及時性。
針對TCP層的發送緩衝,有「優雅關閉、強制關閉」兩種。優雅關閉是指,若是發送緩存中還有數據未發出則其發出去,而且收到全部數據的ACK以後,發送FIN包,開始關閉過程;強制關閉是指,若是緩存中還有數據,則丟棄這些數據,而後發送RST包,直接重置TCP鏈接。
針對用戶層的發送緩衝,正統作法是:將closeflag置true,業務層的buffer再也不接數據了,繼續調tcpSend直至buffer數據被髮完,隨即「優雅關閉」——TCP緩衝發完後FIN,客戶端收到後回FIN(四次握手關閉鏈接),svr收到FIN,進而closesocket。
客戶端異常就收不到回的FIN了,因此得要個「優雅關閉列表」,數秒後還不關就強關。
還有土豪作法,先將鏈接設置成無效的,業務層不收也不發了,等足夠長時間(3分鐘)後,強關~~