前言
作者:小蝸牛向前沖
名言:我可以接受失敗,但我不能接受放棄
??如果覺的博主的文章還不錯的話,還請
點贊,收藏,關注👀支持博主。如果發現有問題的地方歡迎?大家在評論區指正?
目錄
一 、TCP協議格式
1、格式框架
2、TCP協議的三次握手和四次揮手的的細節?
二、滑動窗口?
?三、流量控制
四、擁塞控制
五、延遲應答
六、捎帶應答和面向字節流
?七、粘包問題和TCP異常情況
八、 TCP小結
本期學習:TCP協議的格式。TCP協議的機制:滑動窗口,流量控制,擁塞控制,延遲應答,稍帶應答。?TCP協議是面向字節流的,粘包問題和TCP異常情況。
一 、TCP協議格式
TCP全稱為 "傳輸控制協議(Transmission Control Protocol"). 人如其名, 要對數據的傳輸進行一個詳細的控制;
1、格式框架
對于tcp協議他是有自己的標準長度的20個字節 。那我們清楚tcp的協議的報頭是什么嗎?和UDP的結構一樣嗎?
對于tcp協議他的報頭和UDP和一樣也包含了16位的源端口和16位的目的端口(告訴進程,從哪里來,到哪里去),但是他還包含UDP協議沒有的部分,根據下圖一起來了解一下吧!
問題1:我們知道協議其實就是結構化的數據,為將報頭和有效載荷分離,那我們肯定要知道報頭多大,把報頭讀完,后面就是有效載荷?。?而報頭的長度其實就和4位首部長度有關
4位TCP報頭長度 (數據偏移)
它指定了TCP首部的長度,以32位字長為單位。因為TCP頭部長度最大為60個字節,而一個TCP報文段的最大長度是65535字節(64kb),所以TCP首部長度必須編碼成4位,取值范圍為0到15(即0x0到0xF)。這個值乘以4才是TCP頭部的長度,因此TCP頭部最小長度為20字節,最大長度為60字節。
問題2:我們一個tcp協議的報文,從客戶端發送到服務器,是如何保證我報文發送的順序,客戶端是如何知道服務器已經收到我發送的報文的你呢?
這就不得不提TCP協議中結構中32位的序號和32位的確認序號。
?32位的序號和32位的確認序號
序號(Sequence Number):用于對TCP報文段進行唯一編號,使得接收方可以按照正確的順序重新組裝傳輸的數據。序號表示發送方在發送數據時將字節流分成多個報文段時,每個報文段中第一個字節的序號。
確認號(Acknowledgement Number):用于確認已經成功接收到的數據,同時指示接收方期望接收到的下一個字節的序號。確認號表示接收方期望從發送方接收的下一個字節的序號。
我們知道一個數據從客戶端到服務器,本質上是調用了系統的拷貝函數(wirte revc sendto),將客戶端發送緩沖區的數據發送到服務器的接收緩沖區。?
問題3:?但是客戶端在發送數據的怎么知道,服務器的緩沖區還有多大,從而決定我的發送速度?
16位窗口?
TCP協議中的16位窗口是指TCP報文段中的窗口字段,用于進行流量控制。窗口大小表示接收方的緩沖區大小,即接收方能夠接收的數據量。
在TCP通信過程中,發送方會根據接收方的窗口大小來決定發送數據的數量。發送方通過將窗口大小值放置在TCP報文段的窗口字段中,告知接收方可以接收的數據量。接收方通過該窗口字段來控制發送方的發送速率,以避免接收方的緩沖區溢出。?
注意:
- 口大小是動態變化的,它可以根據網絡的擁塞狀況來進行調整。當網絡擁塞時,接收方可以減小窗口大小,限制發送方的發送速率,從而減少網絡負載。而當網絡暢通時,接收方可以增大窗口大小,以提高傳輸效率。
- 窗口大小是以字節為單位的,因此16位窗口字段的取值范圍為0到65535字節。較大的窗口大小可以提供更高的吞吐量,但也會增加網絡擁塞的風險。
?問題4:上面說如果服務器接收到了客戶端發送的數據就回進行應答,但是我們怎么知道服務器收到的是一個完整數據呢?
16位校驗和:
TCP 的 16 位校驗和是 TCP 頭部的一個重要部分,用于檢測數據在傳輸過程中是否發生了錯誤或者數據是否被篡改。
計算 TCP 校驗和的過程如下:
- 將 TCP 報文段按照 16 位(2 字節)為單位進行分割。
- 將分割后的每個 16 位數據字段(以二進制形式表示)相加,得到一個 32 位的中間結果。
- 如果最后還剩下一個 16 位數據字段,將其視為一個 16 位值,添加到中間結果中。
- 如果中間結果的高 16 位不為 0,則將高 16 位和低 16 位相加,直到高 16 位為 0 為止,得到一個 16 位的結果。
- 將這個 16 位的結果按位取反,得到最終的 16 位校驗和。
問題5:我們知道在TCP過程中會經歷3次牽手和4次揮手的過程,?其實會傳輸SNY,FIN類型的報文,這有什么用?
?那其實是報文的類型,比如到有SYN表示確實的類型的報文。
6位標志位:
- URG: 緊急指針是否有效 。
- ACK: 確認號是否有效。
- PSH: 提示接收端應用程序立刻從TCP緩沖區把數據讀走 。
- RST: 對方要求重新建立連接; 我們把攜帶RST標識的稱為復位報文段 。
- SYN: 請求建立連接; 我們把攜帶SYN標識的稱為同步報文段 。
- FIN: 通知對方, 本端要關閉了, 我們稱攜帶FIN標識的為結束報文段。
問題6我們知道報文是發送是有順序的,如果我們有一個緊急報文要操作系統處理,該怎么辦?
16位緊急指針:
標識哪部分數據是緊急數據
2、TCP協議的三次握手和四次揮手的的細節?
問題1:為什么在建立鏈接一定是三次握手,不能是一次或者二次,甚至是N次握手呢?
確認雙方能夠收發數據: 在 TCP 連接建立之前,服務器和客戶端都需要確認對方能夠正常收發數據。第一次握手的作用是客戶端向服務器發送連接請求,第二次握手是服務器收到請求并確認,并向客戶端發送確認,而第三次握手是客戶端收到確認并向服務器發送確認。通過這樣的握手過程,雙方都能夠確信對方能夠收發數據。
防止舊連接請求的影響: 如果采用一次或者兩次握手,可能會導致已經失效的連接請求被錯誤地接受,從而導致不必要的連接建立或其他問題的發生。通過三次握手,可以有效地避免這種情況的發生。
確保雙方的序列號正確: TCP 的連接建立過程中,雙方需要交換各自的初始序列號,用于后續數據的可靠傳輸。三次握手確保了雙方都能夠正確地獲得對方的序列號,并且達成一致。
可靠性和安全性考慮: 通過三次握手,可以在連接建立的過程中進行雙向的確認,提高了連接的可靠性。同時,三次握手也有助于防止惡意的連接請求,提高了連接的安全性。
問題2:?在四次揮手的過程中,客戶端請求斷開鏈接,服務器同意。為什么最后在服務器請求斷開鏈接后,客戶端還能發送ack報文同意呢?鏈接不是早就斷開了嗎?
其實之前斷開鏈接是指不在發送用戶數據,操作系統底層在一定時間能還是有交互的,最終的關閉是有用戶在上層調用close(sock)才關閉。
確認應答(ACK)機制
TCP將每個字節的數據都進行了編號. 即為序列號
每一個ACK都帶有對應的確認序列號, 意思是告訴發送者, 我已經收到了哪些數據; 下一次你從哪里開始發.
超時重傳機制
- ?主機A發送數據給B之后, 可能因為網絡擁堵等原因, 數據無法到達主機B。
- 如果主機A在一個特定時間間隔內沒有收到B發來的確認應答, 就會進行重發
但是, 主機A未收到B發來的確認應答, 也可能是因為ACK丟失了;?
因此主機B會收到很多重復數據. 那么TCP協議需要能夠識別出那些包是重復的包, 并且把重復的丟棄掉. 這時候我們可以利用前面提到的序列號, 就可以很容易做到去重的效果。
那么, 如果超時的時間如何確定?
- 最理想的情況下, 找到一個最小的時間, 保證 "確認應答一定能在這個時間內返回"。
- 但是這個時間的長短, 隨著網絡環境的不同, 是有差異的.。
- 如果超時時間設的太長, 會影響整體的重傳效率。
- 如果超時時間設的太短, 有可能會頻繁發送重復的包。
CP為了保證無論在任何環境下都能比較高性能的通信, 因此會動態計算這個最大超時時間.。
- Linux中(BSD Unix和Windows也是如此), 超時以500ms為一個單位進行控制, 每次判定超時重發的超時 時間都是500ms的整數倍。
- 如果重發一次之后, 仍然得不到應答, 等待 2*500ms 后再進行重傳.。
- 如果仍然得不到應答, 等待 4*500ms 進行重傳.。
- 依次類推, 以指數形式遞增. 累計到一定的重傳次數, TCP認為網絡或者對端主機出現異常, 強制關閉連接
連接管理機制?
?在正常情況下, TCP要經過三次握手建立連接, 四次揮手斷開連接:
服務端狀態轉化:
- [CLOSED -> LISTEN] 服務器端調用listen后進入LISTEN狀態(listen), 等待客戶端連接;
- [LISTEN -> SYN_RCVD] 一旦監聽到連接請求(同步報文段), 就將該連接放入內核等待隊列中, 并向客戶端 發送SYN(syn)確認報文.
- [SYN_RCVD -> ESTABLISHED] 服務端一旦收到客戶端的確認報文, 就進入ESTABLISHED狀態(established), 可以進行 讀寫數據了.
- [ESTABLISHED -> CLOSE_WAIT] 當客戶端主動關閉連接(調用close), 服務器會收到結束報文段, 服務器 返回確認報文段并進入CLOSE_WAIT(close_wait);
- [CLOSE_WAIT -> LAST_ACK] 進入CLOSE_WAIT后說明服務器準備關閉連接(需要處理完之前的數據); 當 服務器真正調用close關閉連接時, 會向客戶端發送FIN, 此時服務器進入LAST_ACK狀態(last_ack), 等待最后一個 ACK到來(這個ACK是客戶端確認收到了FIN)
- [LAST_ACK -> CLOSED] 服務器收到了對FIN的ACK, 徹底關閉連接.
客戶端狀態轉化:?
- [CLOSED -> SYN_SENT] 客戶端調用connect, 發送同步報文段;
- [SYN_SENT -> ESTABLISHED] connect調用成功, 則進入ESTABLISHED狀態(established),, 開始讀寫數據;
- [ESTABLISHED -> FIN_WAIT_1] 客戶端主動調用close時, 向服務器發送結束報文段, 同時進入 FIN_WAIT_1(fin_wait_1);
- [FIN_WAIT_1 -> FIN_WAIT_2] 客戶端收到服務器對結束報文段的確認, 則進入FIN_WAIT_2(fin_wait_2), 開始等待服 務器的結束報文段;
- [FIN_WAIT_2 -> TIME_WAIT] 客戶端收到服務器發來的結束報文段, 進入TIME_WAIT(time_wait), 并發出LAST_ACK;
- [TIME_WAIT -> CLOSED] 客戶端要等待一個2MSL(Max Segment Life, 報文最大生存時間)的時間, 才會 進入CLOSED狀態.
下圖是TCP狀態轉換的一個匯總:
?
- ?較粗的虛線表示服務端的狀態變化情況;
- 較粗的實線表示客戶端的狀態變化情況;
- CLOSED是一個假想的起始點,不是真實狀態
理解TIME_WAIT狀態
?現在做一個測試,首先啟動server,然后啟動client,然后用Ctrl-C使server終止,這時馬上再運行server, 結果是:
?這是因為,雖然server的應用程序終止了,但TCP協議層的連接并沒有完全斷開,因此不能再次監 聽同樣的server端口. 我們用netstat命令查看一下:
- TCP協議規定,主動關閉連接的一方要處于TIME_ WAIT狀態,等待兩個MSL(maximum segment lifetime) 的時間后才能回到CLOSED狀態.
- 我們使用Ctrl-C終止了server, 所以server是主動關閉連接的一方, 在TIME_WAIT期間仍然不能再次監聽 同樣的server端口;
- MSL在RFC1122中規定為兩分鐘,但是各操作系統的實現不同, 在Centos7上默認配置的值是60s;
- 可以通過 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值
想一想, 為什么是TIME_WAIT的時間是2MSL??
- MSL是TCP報文的最大生存時間, 因此TIME_WAIT持續存在2MSL的話.
- 就能保證在兩個傳輸方向上的尚未被接收或遲到的報文段都已經消失(否則服務器立刻重啟, 可能會收到 來自上一個進程的遲到的數據, 但是這種數據很可能是錯誤的);
- 同時也是在理論上保證最后一個報文可靠到達(假設最后一個ACK丟失, 那么服務器會再重發一個FIN. 這 時雖然客戶端的進程不在了, 但是TCP連接還在, 仍然可以重發LAST_ACK);
但是假設我們的服務因為一些原因要重新啟動,但是因為tcp在服務器斷開鏈接會處于TIME_WAIT狀態,認bind綁定失敗,但是我們不可以等待2*MSL時間,這樣對于一些大公司可能會造成巨大的損失,那有什么方法解決嗎??
使用setsockopt()設置socket描述符的 選項SO_REUSEADDR為1, 表示允許創建端口號相同但IP地址不同的多個 socket描述符
?
問題3:如果服務器?出現了大量的CLOSE_WAIT狀態,是由什么引起的?
- 服務器有Bug,沒有做close文件描述符的動作
- 服務器有壓力,可能一直發送信息給clinet,沒有時間close
問題4:為什么我們在第一次交換數據,就可以知道對方緩沖區接收數據的能力?
因為在通信前進行了三次握手,雙方交換窗口的大小。?
- 問題5:我們發送數據,在沒有接收到ACK應答時候要保留數據(為了支持重傳),那數據保持在哪里?
保存在滑動窗口?
二、滑動窗口?
剛才我們討論了確認應答策略, 對每一個發送的數據段, 都要給一個ACK確認應答. 收到ACK后再發送下一個數據段. 這樣做有一個比較大的缺點, 就是性能較差. 尤其是數據往返的時間較長的時候.
既然這樣一發一收的方式性能較低, 那么我們一次發送多條數據, 就可以大大的提高性能(其實是將多個段的等待時 間重疊在一起了)
- ?窗口大小指的是無需等待確認應答而可以繼續發送數據的最大值. 上圖的窗口大小就是4000個字節(四個 段).
- 發送前四個段的時候, 不需要要等待任何ACK, 直接發送;
- 收到第一個ACK后, 滑動窗口向后移動, 繼續發送第五個段的數據; 依次類推;
- 操作系統內核為了維護這個滑動窗口, 需要開辟 發送緩沖區 來記錄當前還有哪些數據沒有應答; 只有確 認應答過的數據, 才能從緩沖區刪掉;
- 窗口越大, 則網絡的吞吐率就越高;
從上面我們知道滑動窗口,是提高數據發送效率的一段空間,那空間是怎么樣的呢?
下面我們姑且認為滑動窗口其實就是線性的一段數組空間
?
對于發送緩沖區來說我我們可以分為四個部分:
- ?已經發送的數據(收到應答)。
- 已經發送數據但是沒有收到應答(滑動窗口)。
- 數據尚未發送。
- 沒有數據,只有空間。
?
問題1:窗口大小是怎么設定的,未來怎么變化?
- ?窗口大小和對方的接收能力(后面還要考慮網絡情況)有關。
- win_start =0,win_end =win_start+tcp_win未來無論窗口怎么滑動,都要保證對方能夠可以接收到。
- 窗口的大小可以向右滑動,也可以保持不動,肯定不會向左滑動。
- 窗口可以變大也可以變小。
?問題2:如果收到應答不是最左側但發送的確認報文,而是中間的或者說結尾的怎么辦?要滑動窗口嗎?
在解決這個問題的時候,我們要了解一下丟包的情況。
情況一: 數據包已經抵達, ACK被丟了
這種情況下, 部分ACK丟了并不要緊, 因為可以通過后續的ACK進行確認;
情況二: 數據包就直接丟了
- 當某一段報文段丟失之后, 發送端會一直收到 1001 這樣的ACK, 就像是在提醒發送端 "我想要的是 1001" 一樣;
- 如果發送端主機連續三次收到了同樣一個 "1001" 這樣的應答, 就會將對應的數據 1001 - 2000 重新發送;
- 這個時候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因為2001 - 7000)接收端其實之前就已 經收到了, 被放到了接收端操作系統內核的接收緩沖區中
知道丟包的情況,下面我們繼續來討論上面的提問:
- ?出現收到應達,不是滑動窗口最前面的數據,則極大可能就是丟包了。
- 我們知道在收到應答的時候會帶上確認序號:ACK seq X+1,表示之前所以的數據全部收到了。
- 則后面數據發送的應答的確認,序號只可能是在丟包的確實序號1000,當重復收到3次以上1000確認序號,會觸發重傳機制。(假設丟包的確認序號為1000)
- 滑動窗口移動的本質是數組下標的更新,數組下標和確認序號有關。
- 由于丟包序號一直不變,所以滑動窗口不移動,等待重傳機制的觸發。
?問題3:如果滑動窗口一直移動,沒有空間了怎么辦?
這是不可能發生的,因為緩沖區內核組織是環型結構
?
?
?三、流量控制
?接收端處理數據的速度是有限的. 如果發送端發的太快, 導致接收端的緩沖區被打滿, 這個時候如果發送端繼續發送, 就會造成丟包, 繼而引起丟包重傳等等一系列連鎖反應. 因此TCP支持根據接收端的處理能力, 來決定發送端的發送速度. 這個機制就叫做流量控制(Flow Control);
- 接收端將自己可以接收的緩沖區大小放入 TCP 首部中的 "窗口大小" 字段, 通過ACK端通知發送端;
- 窗口大小字段越大, 說明網絡的吞吐量越高; 接收端一旦發現自己的緩沖區快滿了, 就會將窗口大小設置成一個更小的值通知給發送端;
- 發送端接受到這個窗口之后, 就會減慢自己的發送速度; 如果接收端緩沖區滿了, 就會將窗口置為0;
- 這時發送方不再發送數據, 但是需要定期發送一個窗口探測數 據段, 使接收端把窗口大小告訴發送端
?
接收端如何把窗口大小告訴發送端呢? 回憶我們的TCP首部中, 有一個16位窗口字段, 就是存放了窗口大小信息; 那么問題來了, 16位數字最大表示65535, 那么TCP窗口最大就是65535字節么? 實際上, TCP首部40字節選項中還包含了一個窗口擴大因子M, 實際窗口大小是 窗口字段的值左移 M 位;
四、擁塞控制
雖然TCP有了滑動窗口這個大殺器, 能夠高效可靠的發送大量的數據. 但是如果在剛開始階段就發送大量的數據, 仍 然可能引發問題. 因為網絡上有很多的計算機, 可能當前的網絡狀態就已經比較擁堵. 在不清楚當前網絡狀態下, 貿然發送大量的數據, 是很有可能引起雪上加霜的. TCP引入 慢啟動 機制, 先發少量的數據, 探探路, 摸清當前的網絡擁堵狀態, 再決定按照多大的速度傳輸數據;
- 此處引入一個概念程為擁塞窗口
- 發送開始的時候, 定義擁塞窗口大小為1;
- 每次收到一個ACK應答, 擁塞窗口加1;
- 每次發送數據包的時候, 將擁塞窗口和接收端主機反饋的窗口大小做比較, 取較小的值作為實際發送的窗 口;
像上面這樣的擁塞窗口增長速度, 是指數級別的. "慢啟動" 只是指初使時慢, 但是增長速度非常快.
- 為了不增長的那么快, 因此不能使擁塞窗口單純的加倍.
- 此處引入一個叫做慢啟動的閾值 。
- 當擁塞窗口超過這個閾值的時候, 不再按照指數方式增長, 而是按照線性方式增長
- 當TCP開始啟動的時候, 慢啟動閾值等于窗口最大值;
- 在每次超時重發的時候, 慢啟動閾值會變成原來的一半, 同時擁塞窗口置回1;
少量的丟包, 我們僅僅是觸發超時重傳; 大量的丟包, 我們就認為網絡擁塞; 當TCP通信開始后, 網絡吞吐量會逐漸上升; 隨著網絡發生擁堵, 吞吐量會立刻下降; 擁塞控制, 歸根結底是TCP協議想盡可能快的把數據傳輸給對方, 但是又要避免給網絡造成太大壓力的折中方案. TCP擁塞控制這樣的過程, 就好像 熱戀的感覺?
五、延遲應答
如果接收數據的主機立刻返回ACK應答, 這時候返回的窗口可能比較小
- 假設接收端緩沖區為1M. 一次收到了500K的數據; 如果立刻應答, 返回的窗口就是500K;
- 但實際上可能處理端處理的速度很快, 10ms之內就把500K數據從緩沖區消費掉了;
- 在這種情況下, 接收端處理還遠沒有達到自己的極限, 即使窗口再放大一些, 也能處理過來;
- 如果接收端稍微等一會再應答, 比如等待200ms再應答, 那么這個時候返回的窗口大小就是1M
一定要記得, 窗口越大, 網絡吞吐量就越大, 傳輸效率就越高. 我們的目標是在保證網絡不擁塞的情況下盡量提高傳輸 效率;
那么所有的包都可以延遲應答么? 肯定也不是;?
- 數量限制: 每隔N個包就應答一次;
- 時間限制: 超過最大延遲時間就應答一次
具體的數量和超時時間, 依操作系統不同也有差異; 一般N取2, 超時時間取200ms;?
六、捎帶應答和面向字節流
捎帶應答
在延遲應答的基礎上, 我們發現, 很多情況下, 客戶端服務器在應用層也是 "一發一收" 的. 意味著客戶端給服務器說 了 "How are you", 服務器也會給客戶端回一個 "Fine, thank you"; 那么這個時候ACK就可以搭順風車, 和服務器回應的 "Fine, thank you" 一起回給客戶端
面向字節流
?創建一個TCP的socket, 同時在內核中創建一個 發送緩沖區 和一個 接收緩沖區
- 調用write時, 數據會先寫入發送緩沖區中;
- 如果發送的字節數太長, 會被拆分成多個TCP的數據包發出;
- 如果發送的字節數太短, 就會先在緩沖區里等待, 等到緩沖區長度差不多了, 或者其他合適的時機發送出 去;
- 接收數據的時候, 數據也是從網卡驅動程序到達內核的接收緩沖區;
- 然后應用程序可以調用read從接收緩沖區拿數據;
- 另一方面, TCP的一個連接, 既有發送緩沖區, 也有接收緩沖區, 那么對于這一個連接, 既可以讀數據, 也可 以寫數據. 這個概念叫做 全雙工
?由于緩沖區的存在, TCP程序的讀和寫不需要一一匹配, 例如
- 寫100個字節數據時, 可以調用一次write寫100個字節, 也可以調用100次write, 每次寫一個字節;
- 讀100個字節數據時, 也完全不需要考慮寫的時候是怎么寫的, 既可以一次read 100個字節, 也可以一次 read一個字節, 重復100次
?七、粘包問題和TCP異常情況
粘包問題
- 首先要明確, 粘包問題中的 "包" , 是指的應用層的數據包.
- 在TCP的協議頭中, 沒有如同UDP一樣的 "報文長度" 這樣的字段, 但是有一個序號這樣的字段.
- 站在傳輸層的角度, TCP是一個一個報文過來的. 按照序號排好序放在緩沖區中. 站在應用層的角度, 看到的只是一串連續的字節數據.
- 那么應用程序看到了這么一連串的字節數據, 就不知道從哪個部分開始到哪個部分, 是一個完整的應用層 數據包
?那么如何避免粘包問題呢? 歸根結底就是一句話, 明確兩個包之間的邊界
- ?對于定長的包, 保證每次都按固定大小讀取即可; 例如上面的Request結構, 是固定大小的, 那么就從緩沖 區從頭開始按sizeof(Request)依次讀取即可;
- 對于變長的包, 可以在包頭的位置, 約定一個包總長度的字段, 從而就知道了包的結束位置; 對于變長的包, 還可以在包和包之間使用明確的分隔符(應用層協議, 是程序猿自己來定的, 只要保證分隔 符不和正文沖突即可)
?思考: 對于UDP協議來說, 是否也存在 "粘包問題" 呢?
- 對于UDP, 如果還沒有上層交付數據, UDP的報文長度仍然在. 同時, UDP是一個一個把數據交付給應用 層. 就有很明確的數據邊界.
- 站在應用層的站在應用層的角度, 使用UDP的時候, 要么收到完整的UDP報文, 要么不收. 不會出現"半 個"的情況
?TCP異常情況
- 進程終止: 進程終止會釋放文件描述符, 仍然可以發送FIN. 和正常關閉沒有什么區別.
- 機器重啟: 和進程終止的情況相同.
- 機器掉電/網線斷開: 接收端認為連接還在, 一旦接收端有寫入操作, 接收端發現連接已經不在了, 就會進行reset. 即 使沒有寫入操作, TCP自己也內置了一個保活定時器, 會定期詢問對方是否還在. 如果對方不在, 也會把連接釋放.
- 另外, 應用層的某些協議, 也有一些這樣的檢測機制. 例如HTTP長連接中, 也會定期檢測對方的狀態. 例如QQ, 在QQ 斷線之后, 也會定期嘗試重新連接.
八、 TCP小結
為什么TCP這么復雜? 因為要保證可靠性, 同時又盡可能的提高性能.
可靠性:
- 校驗和
- 序列號(按序到達)
- 確認應答
- 超時重發
- 連接管理
- 流量控制
- 擁塞控制
提高性能 :
- 滑動窗口
- 快速重傳
- 延遲應答
- 捎帶應答
其他?
定時器(超時重傳定時器, 保活定時器, TIME_WAIT定時器等)