題外話:剛剛過去的半個月實在是忙得我喘不過來氣,雖然手裏還壓着幾個項目得在期末考試以前作完,可是想一想仍是更新一下隨筆,稍微換個心情。另外小吐槽一下那些在博客園裏原封不動抄書當隨筆的人,唉真是....算了我不吐槽了哈哈,進入正題!編程
TCP/IP協議有關的書籍我在圖書館裏翻看了不少,雖然說每本側重都不大同樣,可是有一點是同樣的:TCP協議講義的篇幅都是其餘協議的三到五倍!服務器
下面總結一下TCP協議裏最核心的知識和一些細節,首先從Overview開始:socket
一. 一張圖--TCP FSMtcp
這張圖是TCP協議基礎之基礎之基礎,凝練了TCP鏈接宏觀的狀態的遷移,沒記住這個?嗯,那麼你就確定不懂TCP咯~盯着tcpdump和netstat裏的一條條數據或許能夠幫你背下來這個圖,要注意的是這張圖描述的是TCP鏈接的狀態,從編程的角度來看就是一對套接字(socket)組建起來的鏈接的角度來看的,實線是TCP客戶端鏈接狀態通過的狀態變遷,虛線是服務器端通過的狀態變遷,其中「主動打開」和「被動打開」大部分是從應用程序發起的操做,若是你是用的C/C++的套接字,那麼主動打開就是connect()的動做,而被動打開是listen()的操做,圖中箭頭上的描述是鏈接過程當中的數據的流動,關於細節下文再細說。spa
二. 另外一張圖--Connect Process設計
對於一次正常的TCP鏈接咱們要從兩個角度分別來看鏈接的過程,這和TCP協議的「雙全工」徹底是兩碼事,每條單向的「數據通路」鏈接的兩端都會同時進行如下的狀態變遷:指針
客戶端:CLOSED->SYN_SENT->SYN_RCVD->ESTABLISHED->( FIN_WAIT1->FIN_WAIT2 | FIN_WAIT1 )->TIME_WAIT->CLOSEDblog
服務端:CLOSED->LISTEN->SYN_RCVD->ESTABLISHED->CLOSE_WAIT->LACK_ACK->CLOSED同步
可是隻討論正常狀況下的TCP協議機制是個沒用意義的話題,真正值得思考的是在特殊狀況下的TCP協議狀態的轉換過程,裏面有一些細節是不得不提的,篇幅限制我就羅列一些關鍵字及其信息摘要供你們溫習回憶:博客
連接創建三次握手:
這個真的沒有太多好說的,有點不是理解爲何有人很是強調,這個過程很是的清晰明瞭,和社會中人溝通的方式是類似的:
「阿軍你真帥!」 -> 「謝謝呀,你也很帥!」 -> 「謝謝!」
SYN -> SYN,ACK -> ACK
可是有一點須要拋開來單獨討論的是一種叫作同時打開的情況,儘管這種情況很是的罕見,可是邊界狀況是堅定不能忽略的:
雙方几乎同時發出SYN包,雙方判斷同時打開的條件就是:
在等待SYN+ACK包的時候卻收到了一個SYN包,因而它就會進入SYN_RCVD狀態而且發出一個ACK包,雙方只進行了兩組包的交換就完成了進入ESTABLISHED狀態的過程。
連接關閉三次握手:
什麼?這個也是三次握手?必定和上一個同樣簡單沒內含? 大錯特錯,連接關閉的三次握手過程比連接創建複雜了一個數量級!
和剛纔同樣作我的之間溝通的狀況模擬:
「阿軍我走了阿」 -> "那我也走了阿,再見" -> "再見"
FIN -> FIN,ACK -> ACK
是這個樣子嘛?也許沒錯!可是大多數的狀況是這樣的:
「阿軍我走了阿」 -> "好的,但你先再聽我說兩句" ->(過了一下子)-> 「那我也走了阿」 -> "再見"
FIN -> ACK -> .... -> FIN -> ACK
這是由於TCP協議是一種「雙全工"的協議,TCP連接能夠想象成有兩條方向相反的數據通路之間相互溝通,當一方主動要求關閉連接的時候,另外一方要回復答應關閉一個走向的數據通路可是維持另外一個數據通路繼續進行數據傳輸,直到想要傳輸的數據傳輸完畢再發出一個關閉數據通路的請求而且等待迴應,這個單向數據通路關閉的狀態叫作半關閉狀態。
有半關閉狀態就有半打開狀態,我我的認爲半打開狀態應該放在關閉過程之中進行介紹,當一方已經關閉或者異常終止連接而另外一方不知道,咱們將這樣的連接狀態叫作半打開。
一樣地有同時打開就有同時關閉,類似地當,一方發出FIN包進入FIN_WAIT1狀態的時候若是接受到了另外一方發送的FIN包那麼就算做同時關閉了,雙方直接再次交換ACK報文終止。
下面進入TCP協議的主要細節:
首先要看如下TCP的報文格式,
幾個最關鍵的東西:
(1)窗口大小:
用「滑動」來描述窗口的變化過程是最恰當不過的,不管是發送窗口仍是接受窗口都有左壁和右壁的概念,窗口是在報文緩衝內容上進行所謂滑動的。窗口從客戶端(發送端)的角度來說,窗口應該分爲兩部分,一部分是已經發送可是並未收到確認的報文內容,另外一部分是待發送的報文,在未受到確認的報文內容在計時器(RTO計數器)計數完畢以後仍未受到確認則會啓用報文重發機制從新發送一份報文內容,因此TCP協議是一個可靠的協議,在接受端的接受窗口也會隨着不斷地接受報文的同時發送確認報文,這裏有一個特殊狀況就是當收到的序列號之間出現了斷點或者內容校驗錯誤的時候,會再次發送一個ACK報文使序列號等於下一個但願接受的報文段的序列號(重複的ACK)。
可能產生的死鎖:
有一種可能發生的特殊狀況,確認的丟失可能會引發系統的死鎖。當接受方發送了確認,同時把窗口大小調整爲0,即請求關閉發送窗口時就會發生這樣的狀況。過了一段時間以後,接受方打算取消這一限制,可是若是它沒有數據要發送,就會發送確認包,而且利用一個窗口大小非零的數值來取消這個限制。若是這個確認丟失了,那麼就會產生問題,發送方一直在等待確認一個非零的窗口大小,而接收方則認爲發送方已經收到了這個確認,於是正在等待數據,這種狀況就是典型的死鎖。雙方都是在等待一個純粹的ACK確認,不涉及窗口內報文的確認,因此並無啓用RTO機制來從新發送ACK。要避免死鎖,就要設計一種持續計時器來處理這個問題。
(2)控制字段:六種不一樣的標誌分別用於流量控制,連接創建和終止,連接異常終止及數據傳輸的確認
| URG | ACK | PSH | RST |SYN | FIN |
URG:緊急指針有效
ACK:確認是有效的
PSH:請求確認
RST:鏈接復位
SYN:同步序號
FIN:終止鏈接
(3)序號:本報文段第一個字節的編號,保證鏈接傳送數據的正確性。
(4)確認號:報文段的接受方指望從對方接受的字節編號。
三. FSM模擬僞代碼
TCP-FSM有限狀態機模擬僞代碼:
TCP_Main_Module(segment){ 查找 TCB(TransmitControlBlock) if(相應的TCB未找到) 建立TCB,其狀態爲CLOSED 找到TCB表中相應表項的狀態 swith(狀態){ /// case CLOSED 狀態: if(收到 被動打開 報文)進入LISTEN狀態 if(收到 主動打開 報文){ 發送SYN報文段 進入SYN_SENT狀態 } if(收到任何報文段)發送RST報文段 if(收到其餘任何報文)發出差錯報文 break /// case LISTEN 狀態: if(收到 發送數據 報文){ 發送SYN報文段 進入SYN_SENT狀態 } if(收到 任何SYN報文段 ){ 發送SYN+ACK報文段 進入SYN_RCVD狀態 } if(收到任何其餘報文端或者報文){ 發出發錯報文 } break /// case SYN_SENT 狀態: if(超時)進入CLOSED狀態 if(收到SYN報文段){ 發送SYN+ACK報文段 進入SYN+RCVD狀態 } if(收到SYN+ACK報文段){ 發送ACK報文段 進入ESTABLISHED狀態 } if(收到任何其餘報文段或者報文){ 發出差錯報文 } break case SYN_RCVD 狀態: if(收到ACK報文)進入ESTABLISH狀態 if(超時){ 發送RTS報文 進入CLOSED狀態 } if(收到 關閉 報文){ 進入FIN報文段 進入FIN_WAIT1狀態 } if(收到RTS報文段){ 進入LISTEN狀態 } if(收到任何其餘報文段或者報文){ 發出差錯報文 } break // case ESTABLISHED 狀態: if(收到FIN報文段){ 發送FIN報文段 進入CLOSED-WAIT狀態 } if(收到 關閉 報文){ 發送FIN報文段 進入FIN-WAIT1狀態 } if(收到RTS或者SYN報文段)發出差錯報文 if(收到數據或者ACK報文段)調用輸入模塊 if(收到 發送 報文)調用輸出模塊 break //// case FIN-WAIT1 狀態: if(收到FIN報文段){ 發送ACK報文段 進入CLOSING狀態 } if(收到FIN+ACK報文段){ 發送ACK報文段 進入TIME-WAIT狀態 } if(收到ACK報文段){ 進入FIN-WAIT2狀態 } if(收到任何其餘報文段或者報文){ 發出差錯報文 } break //// case FIN-WAIT2 狀態: if(收到FIN報文段){ 發送ACK報文段 進入TIME-WAIT狀態 } break case CLOSING 狀態: if(收到ACK報文段){ 進入TIME-WAIT狀態 } if(收到任何其餘報文段或者報文){ 發出差錯報文 } break case TIME-WAIT 狀態: if(超時){ 進入CLOSED狀態 } if(收到任何其餘報文段或者報文){ 發出差錯報文 } break case CLOSED-WAIT 狀態: if(收到 關閉 報文){ 發送FIN報文段 進入LAST-ACK狀態 } if(收到任何其餘報文段或者報文){ 發出差錯報文 } break case LAST-ACK 狀態: if(收到ACK報文段){ 進入CLOSED狀態 } if(收到任何其餘報文段或者報文){ 發出差錯報文 } break ///Ending } }