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