TCP,全稱傳輸控制協議(Transmission Control Protocol),是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。
1.1、TCP 服務器調用的 API
#include/* See NOTES */ #include // 1 int socket(int domain, int type, int protocol); // 2 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 3 int listen(int sockfd, int backlog); // 4 ssize_t recv(int sockfd, void *buf, size_t len, int flags); // 5 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // 6 ssize_t send(int sockfd, const void *buf, size_t len, int flags); // 7 int close(int fd); // 8 int shutdown(int sockfd, int how);
1.2、TCP 客戶端調用的 API
#include/* See NOTES */ #include // 1 int socket(int domain, int type, int protocol); // 2 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 3 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // 4 ssize_t send(int sockfd, const void *buf, size_t len, int flags); // 5 ssize_t recv(int sockfd, void *buf, size_t len, int flags); // 6 int close(int fd); // 7 int shutdown(int sockfd, int how);
1.3、API 函數的作用
(1)int socket(int domain, int type, int protocol) 在文件系統中分配一個 fd,并創建 TCB 數據結構。 (2)int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 為 TCP 的 socket 綁定本地 IP 地址和端口。 (3)int listen(int sockfd, int backlog) 將 TCP 置于 LISTEN 狀態。 (4)int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) 從全連接隊列中取出一個節點,并分配一個 fd。 (5)ssize_t recv(int sockfd, void *buf, size_t len, int flags) 在對應 fd 中,從讀緩沖區中拷貝出數據。 (6)ssize_t send(int sockfd, const void *buf, size_t len, int flags) 把 fd 對應的 TCB 數據拷貝到寫緩沖區中。 (7)int close(int fd) 準備一個 FIN 包,放到寫緩沖區,是否 fd。 (8)int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) 準備一個 SYN 包,交給協議棧發送出去,等待三次握手完成后才返回。
二、TCP 的三個階段
2.1 TCP 建立連接
TCP 連接的建立主要依靠 socket ()、bind ()、listen ()、connect ()、accept () 這幾個函數。
2.1.1、TCP 的三次握手
示意圖: 三次握手在 kernel 協議棧中進行,那么三次握手是在哪幾個函數中發送的呢? 第一次,由 connect () 函數觸發 發起握手,也就是發送 syn 包到服務端; 第二次,在 listen () 之后 accept () 之前,服務器接收到 syn 包后發送 syn&&ack 包到客戶端; 第三次,客戶端發送 ack 包到服務端完成連接的建立。 TCP 報頭:
0 |1 |2 |3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-------------------------------+-------------------------------+ | Source Port | Destination Port | +---------------------------------------------------------------+ | Sequence Number | +---------------------------------------------------------------+ | Acknowledgment Number | +-------+-----------+-+-+-+-+-+-+-------------------------------+ | Header| Reserve |U|A|P|R|S|F| Window | | Length| |R|C|S|S|Y|I| | | | |G|K|H|T|N|N| | +-------------------------------+-------------------------------+ | Checksum | Urgent Pointer | +---------------------------------------------------------------+ | Option | +---------------------------------------------------------------+ | Data | | ... | +---------------------------------------------------------------+
SYN:即 synchronous,同步。
ACK:即 acknowledgement,確認。
PSH:即 push,推送。
FIN :即 finish,結束。
RST:即 reset,重置。
URG:即 urgent,緊急。
Sequence Number:是數據包本身第一個字節的序列號。
Acknowledge Number:是期望對方繼續發送的那個確認數據包的序列號其值一般為接收到的 Sequence Number 加 1。
從報文中可以看出,SYN 包最重要的是將 SYN 位設為 1,設置 Sequence Number;ACK 包最重要的是將 ACK 位設為 1,設置 Acknowledgment Number。 半連接隊列和全連接隊列: 在三次握手中,Linux kener 協議棧會維護兩個隊列:半連接隊列和全連接隊列。 半連接隊列(也叫 SYN 隊列):半連接隊列在第一握手中,當客戶端發送 SYN 包到服務端時,服務端的半連接隊列會加入一個節點,表示此連接處于半連接狀態。 全連接隊列(也叫 ACCEPT 隊列):全連接隊列在第三握手中,當客戶端發送 ACK 包到服務端時,服務端會檢查半連接隊列中是否存在此連接節點(通過五元組進行查找),如果存在就將此連接節點加入全連接隊列中;否則將拋棄此連接。 accpt () 函數在三次握手完成后,從全連接隊列中取出連接節點,為節點分配 socket fd,返回到用戶態。 那么,accept () 函數如何知道全連接隊列中有節點呢? 當三次握手完成后,全連接隊列創建節點的同時會釋放一個有連接接入的信號(single 或信號量),這個信號決定了 accept () 函數是否可以從全連接隊列中取節點;也決定 epoll 等 IO 多路復用器能不能檢查這個連接 fd 是否可讀。 在阻塞模式下,accept () 函數一直等待信號,直到全連接隊列中有節點才返回。 在非阻塞模式下,全連接隊列為空 accept () 函數就返回 - 1,否則返回 socket fd。 在 listen () 函數有,有一個 backlog 參數,這個參數表示的是全連接隊列的大小還是半連接隊列的大小呢? 隨著 TCP 協議的不斷迭代,backlog 參數在不同的版本中代表的含義也不相同;它可以是半連接隊列大小,也可以是全連接隊列大小,也可以是半連接隊列 + 全連接隊列的大小總和。不過,效果不會有太大差異。目前版本中主要表示全連接隊列的大小。 DDOS 攻擊: 根據三次握手原理,產生一種對服務器的攻擊方式:DDOS 攻擊。所謂 DDOS 攻擊,就是客戶端偽造一些不存在的 IP,一直發送 SYN 包,使服務器的半連接隊列不斷增大,當半連接隊列的大小達到極限時,造成網絡阻塞就會導致服務器無法再接受連接,從而使服務器奔潰。
2.1.2、TCP 狀態轉換
TCP 狀態轉換圖: (1)從狀態轉換圖看出,LISTEN 狀態可以通過發送 SYN 和數據轉換到 SYN_SEND 狀態;也就是 LISTEN 狀態可以發送數據。 (2)SYN_SEND 狀態可以收到 SYN,并發送 SYN 和 ACK 轉換到 SYN_RECV 狀態;也就是兩個設備可以互發 SYN 包,建立連接。
2.2 TCP 傳輸數據
TCP 傳輸數據主要依靠 send () 和 recv () 兩個函數。 使用 send () 函數發送數據時,返回正數不一定代表發送成功。因為 send () 函數僅僅只是將數據拷貝到協議棧的寫緩沖區,由協議棧發送;發送過程中會經過 N 個網關,可能存在丟包或鏈路斷開導致未能發送到目的地。如果要知道數據是否發送成功,需要加上確認機制(ACK)。
2.2.1、傳輸控制塊 TCB
為了保證數據能正確分發,TCP 使用一種 TCB(傳輸控制塊)的數據結構,把發送給不同設備的數據封裝起來。這個 TCB 會存在整個 TCP 周期,知道斷開連接。 一個 TCB 數據塊包含數據發送雙方對應的 socket 信息以及擁有存放數據的緩沖區。建立連接連接發送數據之前,通信雙方必須做一個準備工作:分配內存建立 TCB 數據塊。當雙方準備好自己的 socket 和 TCB 數據結構后,就可以進入 “三次握手” 建立連接。
2.2.2、TCP 分包
TCP 分包就是要傳輸的數據很大,超出發送緩存區剩余空間,將會進行分包;待發送的數據大于最大報文長度,TCP 在傳輸前將進行分包。 分包在應用程序的處理一般是發送循環 send (),接收方循環 recv ()。
2.2.3、TCP 粘包及解決方案
TCP 粘包就是發送方發送的若干數據包到接收方接收時粘成一個包,從接收緩沖區看就是后數據包的頭緊接著前數據包的尾。 常見解決方案: (1)(推薦)應用層協議頭前面添加包長度。分兩次接收數據;第一次先接收包的長度,然后根據包的長度一次性讀取或循環讀取數據。 例如:
// ... ssize count=0; ssize size=0; while(countlength) { size=recv(fd,buffer,buffersize,0); count+=size; } // ...
(2)為每個包添加分隔符。在數據末尾添加分隔符,這會導致解數據可能需要有合包操作;因為分割數據包后,需要記錄后一個數據包,用于與該包后面部分數據進行合并。
2.3 TCP 四次揮手
斷開連接是比建立連接和傳輸數據還復雜的一個過程,斷開連接主要分為主動關閉和被動關閉兩種。
四次揮手示意圖:
需要注意的是,調用 close () 不是立即完成斷開,而是關閉了數據傳輸,進入了四次揮手階段,TCB 數據結構還沒有釋放。四次揮手結束才真正把 TCB 釋放。
根據四次揮手流程,可以思考一些問題:
(1)傳輸數據過程中,網線斷了之后立刻連接,TCP 如何知道?
網線掉線網卡會停止供電,再次連接后網卡恢復供電,網卡服務重啟,網絡連接重連。應用程序設計通過心跳包檢測。
(2)服務器如何知道客戶端是否宕機?
一樣需要通過心跳包機制來檢測。
(3)服務器如何甄別網絡阻塞和宕機?
服務器發送心跳包時,不僅僅發一次,而是要發送多次的;如果是網絡阻塞,那么在一定時間內一定有回復信息;如果是宕機,無論多長時間都沒有客戶端的回復。
(4)如果出現大量的 CLOSING 狀態,如何處理?
出現大量 CLOSING 狀態,基本上業務上要處理的邏輯過多,導致一直在 CLOSING 狀態;可以使用異步,將網絡層和業務層分離,單獨處理。
(5)四次揮手中,為什么存在 TIME_WAIT 狀態?
防止沒有 LAST_ACK 或 LAST_ACK 丟失,導致一直重發已經不存在的 socket。
總結
需要掌握 TCP 三次握手和四次揮手的過程,熟悉 TCP 狀態轉換。清楚什么是 SYN 包和 ACK 包。
(1)三次握手是 由客戶端發起 SYN,服務端收到 SYN 后發送 SYN 和 ACK,客戶端回復 ACK;完成連接的建立。
(2)斷開連接主要有主動斷開和被動斷開。
(3)四次揮手是 由發起方調用 close (),同時發送 FIN 包;接收端接收到 FIN 包返回 ACK 包,接收端發送 FIN 包;發起方接收到 FIN 包返回 ACK 包;完成斷開。
(4)理解 TCP 的狀態轉換圖。LISTEN 狀態到 SYN_RCVD 狀態和 SYN_SEND 狀態,如何進入 ESTABLISHED 狀態;四次揮手 FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT、CLOSING 直接的轉換,CLOSE_WAIT 和 LAST_ACK 的處理等。
(5)理解 API 的底層原理,以及全連接隊列和半連接隊列。
(6)TCP 的分包場景以及 TCP 粘包的處理方式。
TCP 通信完整過程:
責任編輯:彭菁
-
數據
+關注
關注
8文章
7075瀏覽量
89153 -
網絡協議
+關注
關注
3文章
268瀏覽量
21569 -
TCP
+關注
關注
8文章
1370瀏覽量
79133 -
端口
+關注
關注
4文章
978瀏覽量
32100
原文標題:詳解TCP網絡協議棧的工作原理
文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論