加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

TCP通信機制:三次握手、四次揮手、滑動窗口

12/06 15:12
2032
閱讀需 21 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

1. TCP三次握手

TCP是一種面向連接的安全的流式傳輸協(xié)議,TCP報文的格式如下

標(biāo)志位URG、ACK、PSH、PST、SYN、FIN

SYN:請求建立連接

ACK:給對端應(yīng)答

FIN:斷開連接

16位窗口大?。哼@里的窗口實際上就是滑動窗口(將在后面介紹),這個窗口大小只是記錄了存放數(shù)據(jù)的緩沖區(qū)也就是窗口有多大,而不是實際存放數(shù)據(jù)的地方。

32位序號:在請求建立連接時跟在SYN標(biāo)志位后面的隨機序號。

32位確認(rèn)序號:在應(yīng)答時跟在ACK標(biāo)志位后面的確認(rèn)序號,它的值是對端在建立連接時跟隨在對端發(fā)送的SYN后面的隨機序號的值加上SYN攜帶的數(shù)據(jù)大小再加上1,如果對端SYN未攜帶數(shù)據(jù),則直接在對端SYN后面隨機序號的基礎(chǔ)上加1。比如客戶端發(fā)起連接并攜帶SYN 100(10),服務(wù)端收到后會回復(fù)ACK 111,表示連接請求已收到且10個字節(jié)的數(shù)據(jù)也已收到;如果客戶端發(fā)起的連接為SYN 100,不攜帶數(shù)據(jù),那么服務(wù)端回復(fù)為ACK 101。總之,ACK后面的確認(rèn)序號值為對端SYN隨機序號+攜帶數(shù)據(jù)大小+1。

TCP在建立連接的時候需要進行三次握手(TCP握手時一定有SYN標(biāo)志,不帶SYN標(biāo)志的為建立連接后的正常數(shù)據(jù)傳輸

第一次握手:第一次握手應(yīng)該由客服端發(fā)起,服務(wù)端被動等待連接。

客戶端:主動發(fā)起連接

攜帶標(biāo)志位SYN

產(chǎn)生32位隨機序號:比如100

可以攜帶數(shù)據(jù),也可以不攜帶數(shù)據(jù),攜帶數(shù)據(jù)時要帶上數(shù)據(jù)大小比如10字節(jié) SYN 100(10),不攜帶數(shù)據(jù)為SYN 100。

服務(wù)端:

檢測SYN的值是否為1。如果SYN值為0,則握手失?。蝗绻鸖YN值為1,則握手成功,服務(wù)端回復(fù),進行第二次握手。

第二次握手:服務(wù)端回復(fù)并請求建立反向連接。

服務(wù)端:服務(wù)端發(fā)送確認(rèn)信號ACK并攜帶一個32位的確認(rèn)序號,確認(rèn)序號的值為客戶端SYN隨機序號的值+1,加的這個1實際上是SYN標(biāo)志的大小1字節(jié)。同時,服務(wù)端向客戶端發(fā)起一個反向的連接請求SYN,并攜帶一個32為隨機序號。

確認(rèn)應(yīng)答:ACK標(biāo)志位+32位確認(rèn)序號(值為客戶端隨機序號的值+1+攜帶的數(shù)據(jù)大小)

客戶端不帶數(shù)據(jù):ACK 101 ? ?---> ? 100+1

客戶端攜帶數(shù)據(jù):ACK 111 ? ?---> ? ?100+10+1

發(fā)起一個連接請求:SYN+隨機序號,同樣可以攜帶數(shù)據(jù)也可以不攜帶數(shù)據(jù)

不攜帶數(shù)據(jù):SYN 200(0)

攜帶數(shù)據(jù):SYN 200(10)

客戶端:檢測確認(rèn)標(biāo)志ACK是否為1,并校驗確認(rèn)序號是否正確。

檢測ACK:為1,對端收到并確認(rèn)應(yīng)答。

校驗:不攜帶數(shù)據(jù)時,確認(rèn)序號為101,正好為自己發(fā)送的SYN100的值加1;攜帶數(shù)據(jù)時,確認(rèn)序號為111,說明自己發(fā)送的10字節(jié)數(shù)據(jù)對端已收到。

第三次握手:客戶端向服務(wù)端發(fā)送確認(rèn)數(shù)據(jù)包,完成反向連接的建立

客戶端:確認(rèn)應(yīng)答,ACK+確認(rèn)信號

服務(wù)端未攜帶數(shù)據(jù):ACK 201

服務(wù)端攜帶數(shù)據(jù):ACK 211

服務(wù)端:

檢測ACK是否為1

校驗確認(rèn)序號是否正確

至此,三次握手成功,雙向連接均已建立,可以開始數(shù)據(jù)傳輸了。示意圖如下:

2. TCP四次揮手

TCP斷開連接時需要進行四次揮手:

客戶端與服務(wù)端哪一端主動斷開連接都可以;

揮手時需要一個標(biāo)志位FIN,F(xiàn)IN后面也需要跟一個序號,序號的值為對端最后一次發(fā)送的ACK后面的確認(rèn)序號;

四次揮手的過程如下:

第一次揮手:某一端主動發(fā)起斷開連接的請求。

客戶端:發(fā)送斷開連接的請求

FIN + 序號(對端最后一個ACK后面的確認(rèn)序號)

ACK + 序號(自己上一次ACK后面的確認(rèn)序號)

第二次揮手:另一端確認(rèn)斷開連接。

服務(wù)端:

檢測FIN的值是否為1,如果不是1則揮手失敗,即斷開連接失?。?/p>

ACK + 序號(對端FIN后面的序號+收到數(shù)據(jù)的大小+1),告訴對端對方發(fā)送的數(shù)據(jù)自己收到了多少;

第三次揮手:另一端請求斷開反向連接(TCP是雙向連接,經(jīng)過前兩次揮手只斷開了單向連接,所以需要反向斷開連接)。

服務(wù)端:發(fā)送反向斷開連接的請求。

FIN + 序號(客戶端最后一次ACK所攜帶的確認(rèn)序號);

ACK + 序號(自己上一次ACK后面的確認(rèn)序號)

第四次揮手:TCP雙向連接斷開。

客戶端:客戶端對服務(wù)端發(fā)送的斷開連接請求進行確認(rèn)。

ACK + 序號(對端FIN后面的序號+收到數(shù)據(jù)的大小+1)

 

 

3. TCP連接與數(shù)據(jù)傳輸過程

首先看一個TCP連接與數(shù)據(jù)傳輸?shù)氖疽鈭D

在圖中:

- 1-3:三次握手階段(握手階段一定有SYN標(biāo)志)

- 4-6:數(shù)據(jù)傳輸階段

- 7-10:四次揮手階段(揮手階段一定有FIN標(biāo)志)

圖中的<mss 1460>表示最大數(shù)據(jù)長度,即告知對端給我發(fā)送數(shù)據(jù)的時候不要超過這個最大長度。

詳細(xì)分析上述過程的完整圖示如下

4. TCP滑動窗口機制

首先看滑動窗口的示意圖

在圖中,發(fā)送端速度快,接收端速度慢,一般來說誰先發(fā)送SYN誰就是客戶端,因為客戶端總是主動連接服務(wù)端,而服務(wù)端則被動等待客戶端的連接。

在TCP中,滑動窗口實際上就是一塊緩沖區(qū)(緩存)。在上圖中,客戶端與服務(wù)端進行數(shù)據(jù)傳輸?shù)臅r候總是帶有一個win 4096或win 6144等標(biāo)志,這個win就代表滑動窗口的意思,而后面的數(shù)字則代表滑動窗口所表示的緩存區(qū)的大小。比如客戶端發(fā)起的第一條握手請求,即fast sender所代表的1處,SYN, 0(0), win 4096, <mss 1460>

SYN表示請求建立連接

0(0)表示隨機序號為0,攜帶數(shù)據(jù)大小為0

win 4096 表示滑動窗口大小為4096字節(jié),即4KB,可以參考圖中右側(cè)虛線框起來的部分

<mss 1460>表示允許對端一次發(fā)送的最大數(shù)據(jù)長度為1460字節(jié)

通過server端發(fā)送的 win 6144, <mss 1024> 可知,服務(wù)端滑動窗口大小為6KB,一次可以接受1KB數(shù)據(jù)。client端發(fā)送數(shù)據(jù)的速度是要快于server端接收數(shù)據(jù)的速度的,所以client端會發(fā)送多條數(shù)據(jù),其中每條數(shù)據(jù)為1KB(由server端的mss指定最大接收長度),總共發(fā)送的數(shù)據(jù)不能超過6KB(server端的滑動窗口大小)。所以上圖中client端總共發(fā)送了6條數(shù)據(jù),每條數(shù)據(jù)長度為1KB(可見圖中序號2-9數(shù)據(jù)傳輸),正好大小總共為6KB,等于server端窗口大小(實際上是server端緩沖區(qū)的大小)。

此時,server端的緩沖區(qū)已經(jīng)滿了,不能再接收數(shù)據(jù)了,只有當(dāng)server端把數(shù)據(jù)讀出的時候,緩沖區(qū)才能空出位置接受新數(shù)據(jù)。當(dāng)server端讀取了2K數(shù)據(jù),那么server端的緩沖區(qū)將空余出2K字節(jié),請見上圖中的序號10處,server端向client端回復(fù) ACK,6145, win 2048,表示server端收到了6144字節(jié)(6KB)數(shù)據(jù),6145是由6144+1來的,win 2048表示server端現(xiàn)在緩沖區(qū)空余2K空間。當(dāng)server端再次讀出2K數(shù)據(jù)的時候,server再次向client發(fā)送一條數(shù)據(jù) ACK,6145, win 4096,這里ACK和確認(rèn)序號不變,win所代表的窗口大小發(fā)生了變化,變成了當(dāng)前空余的緩沖區(qū)大小4KB,可見上圖中序號11所代表的數(shù)據(jù)傳輸,此時在server發(fā)送數(shù)據(jù)10和11之間,client端并沒有發(fā)送數(shù)據(jù),但是server端要向client端告知自己的空閑緩沖區(qū)大小。

最后,12-18的過程實際上就是四次揮手的過程,client發(fā)送FIN斷開連接,此時server還沒有處理完緩沖區(qū)中的數(shù)據(jù),所以每處理一批數(shù)據(jù)都會向client發(fā)送一條信息并告知緩沖區(qū)剩余大小,直到緩沖區(qū)中的數(shù)據(jù)全部處理完畢,server向client發(fā)送FIN斷開連接。實際上滑動窗口就是緩沖區(qū)的大小,并且在發(fā)送數(shù)據(jù)過程中,并不是client發(fā)一條server就必須收一條,也可以發(fā)多條收多條,這是因為TCP是流式傳輸,一端發(fā)送的數(shù)據(jù)雖然沒有立即被處理,但是已經(jīng)存起來了,就存在了滑動窗口所表示的緩沖區(qū)中,當(dāng)緩沖區(qū)滿了,發(fā)送端就會臨時阻塞,等待接收端緩沖區(qū)出現(xiàn)剩余空間。

上面所描述的過程都是所有數(shù)據(jù)傳輸都成功的前提下進行的,實際上,每條數(shù)據(jù)的發(fā)送都可能會失敗,當(dāng)發(fā)生傳輸失敗的情況,TCP會進行重傳,重傳的示意圖如下:

5. server服務(wù)端與client客戶端編程實現(xiàn)

server.c

/************************************************************  >File Name  : server.c  >Author     : Mindtechnist  >Company    : Mindtechnist  >Create Time: 2022年08月14日 星期日 19時53分24秒************************************************************/#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <string.h>#include <arpa/inet.h>#include <ctype.h>
int main(int argc, char* argv[]){ ?//創(chuàng)建用于監(jiān)聽的套接字 ?int lfd = socket(AF_INET, SOCK_STREAM, 0); ?if(lfd == -1) ?{ ? ?perror("socket err"); ? ?exit(1); ?} ?
 ?//bind ?struct sockaddr_in server_addr; ?//init ?memset(&server_addr, 0, sizeof(server_addr)); ? ?//bzero(&server_addr, sizeof(serve_addr)); ?server_addr.sin_family = AF_INET; //IPv4 ?server_addr.sin_port = htons(8765); ?server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //使用本機任意IP
 ?int ret = bind(lfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); ?if(ret == -1) ?{ ? ?perror("bind err"); ? ?exit(1); ?}
 ?//設(shè)置監(jiān)聽 ?ret = listen(lfd, 128); ?if(ret == -1) ?{ ? ?perror("listen err"); ? ?exit(1); ?} ? ?//等待并接受連接請求 ?struct sockaddr_in client_addr; ?socklen_t client_len = sizeof(client_addr); ?int cfd = accept(lfd, (struct sockaddr*)&client_addr, &client_len); ?if(cfd == -1) ?{ ? ?perror("accept err"); ? ?exit(1); ?} ?  ?char ipbuf[64]; ?printf("client ip: %s, port: %dn", ? ? ?inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)), ? ? ?ntohs(client_addr.sin_port));
 ?//通信 ?while(1) ?{ ? ?//接收數(shù)據(jù) ? ?char buf[1024] = {0}; ? ?int len = read(cfd, buf, sizeof(buf)); ? ?if(len < 0) ? ?{ ? ? ?perror("read err"); ? ? ?break; ? ?} ? ?else if(len == 0) ? ?{ ? ? ?printf("client disconnect ...n"); ? ? ?break; ? ?} ? ? ?else  ? ?{ ? ? ?//讀到數(shù)據(jù) ? ? ?printf("read buf : %sn", buf); ? ? ?for(int i = 0; i < len; i++) ? ? ?{ ? ? ? ?buf[i] = toupper(buf[i]); ? ? ?} ? ? ?printf("toupper read buf: %sn", buf); ? ? ?//發(fā)送數(shù)據(jù) ? ? ?write(cfd, buf, strlen(buf) + 1); ? ?} ?}
 ?close(lfd); ?close(cfd); ? ?return 0;}

 

client.c

/************************************************************  >File Name  : client.c  >Author     : Mindtechnist  >Company    : Mindtechnist  >Create Time: 2022年08月15日 星期一 11時13分29秒 ************************************************************/#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <string.h>#include <arpa/inet.h>#include <fcntl.h>
int main(int argc, char* argv[]){ ?if(argc < 2) ?{ ? ?printf("err:./exe portn"); ? ?return -1; ?} ?int port = atoi(argv[1]);
 ?//創(chuàng)建套接字 ?int fd = socket(AF_INET, SOCK_STREAM, 0); ?if(fd == -1) ?{ ? ?perror("socket err"); ? ?exit(1); ?}
 ?//連接服務(wù)器 ?struct sockaddr_in server_addr; ?memset(&server_addr, 0, sizeof(server_addr)); ?server_addr.sin_family = AF_INET; ?inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr); ?int ret = connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)); ?if(ret == -1) ?{ ? ?perror("connect err"); ? ?exit(1); ?}
 ?//通信 ?while(1) ?{ ? ?//接收鍵盤輸入 ? ?char buf[512]; ? ?printf("please input string: n"); ? ?fgets(buf, sizeof(buf), stdin); ? ?//發(fā)送給服務(wù)器 ? ?write(fd, buf, strlen(buf));
 ? ?//接收服務(wù)端數(shù)據(jù) ? ?int len = read(fd, buf, sizeof(buf)); ? ?if(len < 0) ? ?{ ? ? ?perror("read err"); ? ? ?exit(1); ? ?} ? ?else if(len == 0) ? ?{ ? ? ?printf("server close connect ...n"); ? ? ?break; ? ?} ? ?else ? ?{ ? ? ?printf("read buf: %s, buflen: %dn", buf, len); ? ?} ?} ?
 ?close(fd);
 ?return 0;}

 

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

Linux、C、C++、Python、Matlab,機器人運動控制、多機器人協(xié)作,智能優(yōu)化算法,貝葉斯濾波與卡爾曼濾波估計、多傳感器信息融合,機器學(xué)習(xí),人工智能。