深入理解 tcp 協議(一)

Max發表於2018-03-26

三路握手

  1. 伺服器端通過呼叫 socket、bind和listen這3個函式準備好接受外來的連線。稱之為被動開啟(passive open)

  2. 客戶端通過呼叫 connect發起主動開啟( active open)。該呼叫使客戶端傳送一個SYN(同步)分節給伺服器,SYN中包含客戶將在(待建立的)連線中傳送的資料的初始序列號。通常SYN分節不攜帶資料, 其所在IP資料包只含有一個IP首部、一個TCP首部及可能有的TCP選項。
  3. 伺服器必須確認(ACK)客戶的SYN,同時自己也得傳送一個SYN分節,它含有伺服器將在同一連線中傳送的資料的初始序列號。伺服器在單個分節中傳送SYN和對客戶SYN的ACK(確認)。
  4. 客戶必須確認伺服器的SYN。既傳送一個確認SYN的ACK。

這種交換至少需要3個分組,因此稱之為TCP的三路握手(thre-way handshake)。

可以看到服務端呼叫accept()函式後,一直處於阻塞狀態。直到三路握手完成後,accept函式才返回。然後再阻塞於read狀態

TCP狀態轉換圖

TCP為一個連線定義了11種狀態

其中ESTABLISHED為(客戶端或服務端)完成三路握手後的狀態,這個最終狀態在資料傳送期間一直保持。

主動呼叫close一方經歷了TIME_WAIT狀態, 在TIME_WAIT狀態下會持續 2MSL (時間單位)。
MSL: maximum segment lifetime
實現TCP時必須必須為MSL選擇一個值,建議在30秒~2分鐘。
MSL是任何IP資料包能夠在因特網中存活的最長時間。

TIME_WAIT狀態有兩個存在的理由:
(1)可靠地實現TCP全雙工連線的終止
(2)允許老的重複分節在網路中消逝,因為TCP協議規定在TIME_WAIT狀態下的一端不允許建立新的TCP連線。從而保證舊連線的任何分節都不會傳送到新的連線中去(分節在因特網中的最長存活時間小於TIME_WAIT持續的時間)。

上圖伺服器對客戶的請求的確認是伴隨其應答傳送的,這種做法稱為 piggybacking,
它通常在伺服器請求併產生的應答的時間少於200ms時發生。

併發伺服器


如圖為一個多程式模型的併發伺服器

我們發現子程式1和子程式2的connect_socket甚至是listen_socket都使用了伺服器端的21埠。

當客戶端請求12.106.32.254:21埠時,tcp協議應該將該請求交給三個socket中的哪一個呢?

首先通過上圖可以確定的是無法僅僅通過目的埠將到來的請求交給對應的socket。

書(unix網路程式設計卷3)中的解釋是: TCP協議必須檢視套接字對的所有四個元素,才能確定由哪個socket接受到達的客戶端請求。

對於tcp請求,我們可以在分節的ip標頭和tcp標頭中得到這四個元素(請求雙方的id和埠號)

下面是我根據書中的解釋做出的臆想

對於一臺併發伺服器來說,一個簡單的演算法是:對於一個到來的分節。首先檢視其目的埠(對於單ip伺服器來說,再檢視分節的目的ip就有些多此一舉了)。

然後檢索所有監聽了該目的埠的socket。假如只檢索到一個,那麼直接將該分節交給該socket即可。

當檢索出了多個socket時 (上圖中多個已連線socket的情況),我們可以根據客戶端的ip與埠號進行匹配,當然這一次是客戶端的ip與埠都需要參與匹配。
優先匹配具有精確客戶端地址的socket 如伺服器的子程式2的socket中儲存的socket pair為 {12.106.32.254:21,206.168.112.219:1501}
其精確的標識了請求端的ip地址和埠號 206.168.112.219:1501
其次再匹配通配的socket。如監聽socket

當然要做到上面匹配需要我們伺服器端的socket中儲存了4個元素。實際上也確實如此

socket中包含的一個inet_sock的結構體如下。其中包含確定一個tcp連線所需的4個元素

struct inet_sock   
{   
    struct sock sk;   
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)   
    struct ipv6_pinfo   *pinet6;   
#endif   
    __u32           daddr;          //IPv4的目的地址。   
    __u32           rcv_saddr;      //IPv4的本地接收地址。   
    __u16           dport;          //目的埠。   
    __u16           num;            //本地埠(主機位元組序)。  

    …………      
}

相關文章