【TCP/IP】TCP的三次握手,四次揮手過程詳解
TCP傳輸控制協議
TCP是一個面向連線的協議,為使用者程序提供可靠的全雙工位元組流。TCP套接字是一種流套接字,TCP關心確認、超時和重傳之類的細節。
首先,TCP提供客戶與伺服器之間的連線。TCP客戶先與某個給定伺服器建立一個連線,再跨該連線與那個伺服器交換資料,然後終止這個連線。
可靠性
其實TCP還提供了可靠性。當TCP向另一端傳送資料時,他要求對端返回一個確認,如果沒有收到確認,TCP就自動重傳資料並等待更長時間。在數次重傳失敗後,TCP才放棄,如此在嘗試傳送資料上所花的總時間一般為4-10分鐘。
動態估算時間
TCP含有用於動態估算客戶和伺服器之間的往返時間——round-trip time,即RTT的演算法,以便知道等待一個確認需要多少時間,舉例來說,RTT在一個區域網上大概是幾毫秒,跨越一個廣域網可能需要幾秒鐘。
排序
TCP通過給其他每個位元組關聯一個序列號對所傳送的資料進行排序。
舉例來說:
如果一個應用程序寫2048位元組到一個TCP套接字,導致TCP傳送2個分節:第一個分節所含資料的序列號為1~1024,第二個分節所含資料的序列號為1025~2048。
那麼假如這兩個分節非順序到達,即第二個分節先到達,那麼接收端TCP將先根據它們的序列號重新排序,再把結果資料傳遞給接受應用。
如果接收端TCP接收到來自對端的重複資料,他可以根據序列號判斷資料是重複的,從而丟棄重複資料。
流量控制
此外,TCP還提供流量控制。TCP總是告知對端在任何時刻他一次能夠從對端接收多少位元組的資料,這稱為通告視窗
在任何時刻,該視窗指出接受緩衝區中當前可用的空間量,從而確保傳送端傳送的資料不會是接受緩衝區溢位。
該視窗時刻動態變化:
當接收到來自發送端的資料時,視窗大小就減小,但是當接收端應用從緩衝區中讀取資料時,視窗大小就增大。通告視窗大小減小到0是有可能的——當TCP對應某個套接字的接受緩衝區已滿,導致它必須等待應用從該緩衝區讀取資料時,方能從對端再接收資料。
TCP連線的建立和終止
TCP連線建立——三路握手
建立一個TCP連線時會發生如下事件:
伺服器必須準備好接受外來的連線。這通常通過呼叫socket,bind,listen這3個函式來完成,我們稱之為被動開啟
客戶通過呼叫connect發起主動開啟,這導致客戶TCP傳送一個SYN(同步)分節,他告訴伺服器客戶將在待建立的連線中傳送的資料的初始化序列號。通常SYN分節不攜帶資料,其所在IP資料報只含有一個IP首部,一個TCP首部及可能有TCP選項。
伺服器必須確認ACK客戶的SYN,同時自己也得傳送一個SYN分節,它含有伺服器將在同一連線中傳送的資料的初始序列號。伺服器在單個分節中傳送SYN和對客戶SYN的ACK。
客戶必須確認伺服器的SYN。
連線過程如下圖所示:
這種交換至少需要三個分組,因此稱之為TCP的三路握手。
上圖給出的客戶的初始序列號為J,伺服器的初始序列號為K。ACK中的確認好是傳送這個ACK的一端所期待的下一個序列號。
因為SYN佔據一個位元組的序列號空間,所以每一個SYN的ACK中的確認號就是該SYN的初始序列號加一。類似的每一個FIN(表示結束)的ACK中的確認號為該FIN的序列號加一。
socket
為了執行網路I/O,一個程序必須做的第一件事就是呼叫socket函式,指定期望的通訊協議型別。
#include <sys/socket.h>
int socket(int family,int type,int protocol);
引數:
- family引數指明協議族
- type引數指明套接字型別
- protocol引數設為某個協議型別常值,或者設為0,已選擇給定family和type組合的系統預設值
返回值
socket函式在成功時返回一個小的非負整數值,他與檔案描述符型別,我們將他成為套接字描述符。
bind
bind函式把一個本地協議地址賦予一個套接字。
int bind(int sockfd,
const struct sockaddr *myaddr,socklen_t addrlen);
引數
- 第一個引數就是要被賦值的套接字
- 第二個引數是一個指向特定與協議的地址結構的指標
第三個引數是改地址結構的長度
對於TCP,呼叫bind函式可以指定一個埠號,或指定一個IP地址,可以二者都指定或者而這都不指定
伺服器在啟東時捆綁他們的眾所周知埠,如果一個TCP客戶或伺服器未曾呼叫bind捆綁一個埠,當呼叫connect或listen函式時,核心就要為相應的套接字選擇一個臨時埠,讓核心來選擇臨時埠對TCP客戶來說是正常的。
程序可以把一個特定的IP地址捆綁到他的套接字上,不過這個IP地址必須屬於其所在主機的網路介面之一。
對於TCP客戶,這就為在該套接字上傳送的IP資料報只拍了源IP地址。
對於TCP伺服器,這就限定該套接字只接受那些目的地為這個IP地址的客戶連線。
listen
int listen(int sockfd,int backlog)
listen函式僅由TCP伺服器呼叫,他做兩件事:
- listen函式把一個未連線的套接字轉換成一個被動套接字,指示核心應接受指向該套接字的連線請求
- 本函式第二個引數規定了核心應為相應套接字排隊的最大連線個數
backlog引數
核心為任何一個給定的監聽套接字維護兩個佇列:
1.未完成連線佇列:每個這樣的SYN分節對應其中意向:已由某個客戶發出併到達伺服器,而伺服器正在等待完成相應的TCP三路握手過程
2.已完成連線佇列,每個已完成TCP三路握手過程的客戶對應其中一項。
TCP連線終止——四次揮手
TCP建立一個連線需要三個分節,終止一個連線則需要四個分節。
- 某個應用程序首先呼叫close,我們稱該端執行主動關閉。該端的TCP是傳送一個FIN分節,表示資料傳送完畢。
- 接收到這個FIN的對端執行被動關閉。這個FIN由TCP確認,他的接受也作為一個檔案結束符EOF傳遞給接收端應用程序(放在已排隊等候該應用程序接收的任何其他資料之後),因為FIN的接收意味著接收端應用程序在相應連線上再無額外資料可接收。
- 一段時間後,接收到這個檔案結束符的應用程序將呼叫close關閉他的套接字,這導致他的TCP也傳送一個FIN。
- 接收這個最終FIN的原發送端TCP,即執行主動關閉的那一端確認這個FIN。
如上圖,當套接字被關閉時,其所在端TCP各自發送了一個FIN。
需要注意的是:
當一個Unix程序無論是自願的呼叫exit或從main函式return,還是非自願的如收到一個終止本程序的訊號而終止時,所有開啟的描述符都被關閉,這就導致仍然開啟的任何TCP連線上也發出一個FIN。
在四次揮手中會出現TIME_WAIT狀態,關於這個狀態在我上一篇博文【TCP/IP】TIME_WAIT狀態及地址reuse問題,SO_REUSEADDR引數詳解中有所解釋。