1. 程式人生 > >【TCP/IP】TCP的三次握手,四次揮手過程詳解

【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引數詳解中有所解釋。