1. TCP三次握手
TCP是一種面向連接的安全的流式傳輸協(xié)議,TCP報(bào)文的格式如下
標(biāo)志位URG、ACK、PSH、PST、SYN、FIN
SYN:請求建立連接
ACK:給對端應(yīng)答
FIN:斷開連接
16位窗口大?。哼@里的窗口實(shí)際上就是滑動(dòng)窗口(將在后面介紹),這個(gè)窗口大小只是記錄了存放數(shù)據(jù)的緩沖區(qū)也就是窗口有多大,而不是實(shí)際存放數(shù)據(jù)的地方。
32位序號:在請求建立連接時(shí)跟在SYN標(biāo)志位后面的隨機(jī)序號。
32位確認(rèn)序號:在應(yīng)答時(shí)跟在ACK標(biāo)志位后面的確認(rèn)序號,它的值是對端在建立連接時(shí)跟隨在對端發(fā)送的SYN后面的隨機(jī)序號的值加上SYN攜帶的數(shù)據(jù)大小再加上1,如果對端SYN未攜帶數(shù)據(jù),則直接在對端SYN后面隨機(jī)序號的基礎(chǔ)上加1。比如客戶端發(fā)起連接并攜帶SYN 100(10),服務(wù)端收到后會(huì)回復(fù)ACK 111,表示連接請求已收到且10個(gè)字節(jié)的數(shù)據(jù)也已收到;如果客戶端發(fā)起的連接為SYN 100,不攜帶數(shù)據(jù),那么服務(wù)端回復(fù)為ACK 101??傊珹CK后面的確認(rèn)序號值為對端SYN隨機(jī)序號+攜帶數(shù)據(jù)大小+1。
TCP在建立連接的時(shí)候需要進(jìn)行三次握手(TCP握手時(shí)一定有SYN標(biāo)志,不帶SYN標(biāo)志的為建立連接后的正常數(shù)據(jù)傳輸)
第一次握手:第一次握手應(yīng)該由客服端發(fā)起,服務(wù)端被動(dòng)等待連接。
客戶端:主動(dòng)發(fā)起連接
攜帶標(biāo)志位SYN
產(chǎn)生32位隨機(jī)序號:比如100
可以攜帶數(shù)據(jù),也可以不攜帶數(shù)據(jù),攜帶數(shù)據(jù)時(shí)要帶上數(shù)據(jù)大小比如10字節(jié) SYN 100(10),不攜帶數(shù)據(jù)為SYN 100。
服務(wù)端:
檢測SYN的值是否為1。如果SYN值為0,則握手失敗;如果SYN值為1,則握手成功,服務(wù)端回復(fù),進(jìn)行第二次握手。
第二次握手:服務(wù)端回復(fù)并請求建立反向連接。
服務(wù)端:服務(wù)端發(fā)送確認(rèn)信號ACK并攜帶一個(gè)32位的確認(rèn)序號,確認(rèn)序號的值為客戶端SYN隨機(jī)序號的值+1,加的這個(gè)1實(shí)際上是SYN標(biāo)志的大小1字節(jié)。同時(shí),服務(wù)端向客戶端發(fā)起一個(gè)反向的連接請求SYN,并攜帶一個(gè)32為隨機(jī)序號。
確認(rèn)應(yīng)答:ACK標(biāo)志位+32位確認(rèn)序號(值為客戶端隨機(jī)序號的值+1+攜帶的數(shù)據(jù)大小)
客戶端不帶數(shù)據(jù):ACK 101 ? ?---> ? 100+1
客戶端攜帶數(shù)據(jù):ACK 111 ? ?---> ? ?100+10+1
發(fā)起一個(gè)連接請求:SYN+隨機(jī)序號,同樣可以攜帶數(shù)據(jù)也可以不攜帶數(shù)據(jù)
不攜帶數(shù)據(jù):SYN 200(0)
攜帶數(shù)據(jù):SYN 200(10)
客戶端:檢測確認(rèn)標(biāo)志ACK是否為1,并校驗(yàn)確認(rèn)序號是否正確。
檢測ACK:為1,對端收到并確認(rèn)應(yīng)答。
校驗(yàn):不攜帶數(shù)據(jù)時(shí),確認(rèn)序號為101,正好為自己發(fā)送的SYN100的值加1;攜帶數(shù)據(jù)時(shí),確認(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
校驗(yàn)確認(rèn)序號是否正確
至此,三次握手成功,雙向連接均已建立,可以開始數(shù)據(jù)傳輸了。示意圖如下:
2. TCP四次揮手
TCP斷開連接時(shí)需要進(jìn)行四次揮手:
客戶端與服務(wù)端哪一端主動(dòng)斷開連接都可以;
揮手時(shí)需要一個(gè)標(biāo)志位FIN,F(xiàn)IN后面也需要跟一個(gè)序號,序號的值為對端最后一次發(fā)送的ACK后面的確認(rèn)序號;
四次揮手的過程如下:
第一次揮手:某一端主動(dòng)發(fā)起斷開連接的請求。
客戶端:發(fā)送斷開連接的請求
FIN + 序號(對端最后一個(gè)ACK后面的確認(rèn)序號)
ACK + 序號(自己上一次ACK后面的確認(rèn)序號)
第二次揮手:另一端確認(rèn)斷開連接。
服務(wù)端:
檢測FIN的值是否為1,如果不是1則揮手失敗,即斷開連接失??;
ACK + 序號(對端FIN后面的序號+收到數(shù)據(jù)的大小+1),告訴對端對方發(fā)送的數(shù)據(jù)自己收到了多少;
第三次揮手:另一端請求斷開反向連接(TCP是雙向連接,經(jīng)過前兩次揮手只斷開了單向連接,所以需要反向斷開連接)。
服務(wù)端:發(fā)送反向斷開連接的請求。
FIN + 序號(客戶端最后一次ACK所攜帶的確認(rèn)序號);
ACK + 序號(自己上一次ACK后面的確認(rèn)序號)
第四次揮手:TCP雙向連接斷開。
客戶端:客戶端對服務(wù)端發(fā)送的斷開連接請求進(jìn)行確認(rèn)。
ACK + 序號(對端FIN后面的序號+收到數(shù)據(jù)的大小+1)
3. TCP連接與數(shù)據(jù)傳輸過程
首先看一個(gè)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ù)的時(shí)候不要超過這個(gè)最大長度。
詳細(xì)分析上述過程的完整圖示如下
4. TCP滑動(dòng)窗口機(jī)制
首先看滑動(dòng)窗口的示意圖
在圖中,發(fā)送端速度快,接收端速度慢,一般來說誰先發(fā)送SYN誰就是客戶端,因?yàn)榭蛻舳丝偸侵鲃?dòng)連接服務(wù)端,而服務(wù)端則被動(dòng)等待客戶端的連接。
在TCP中,滑動(dòng)窗口實(shí)際上就是一塊緩沖區(qū)(緩存)。在上圖中,客戶端與服務(wù)端進(jìn)行數(shù)據(jù)傳輸?shù)臅r(shí)候總是帶有一個(gè)win 4096或win 6144等標(biāo)志,這個(gè)win就代表滑動(dòng)窗口的意思,而后面的數(shù)字則代表滑動(dòng)窗口所表示的緩存區(qū)的大小。比如客戶端發(fā)起的第一條握手請求,即fast sender所代表的1處,SYN, 0(0), win 4096, <mss 1460>
SYN表示請求建立連接
0(0)表示隨機(jī)序號為0,攜帶數(shù)據(jù)大小為0
win 4096 表示滑動(dòng)窗口大小為4096字節(jié),即4KB,可以參考圖中右側(cè)虛線框起來的部分
<mss 1460>表示允許對端一次發(fā)送的最大數(shù)據(jù)長度為1460字節(jié)
通過server端發(fā)送的 win 6144, <mss 1024> 可知,服務(wù)端滑動(dòng)窗口大小為6KB,一次可以接受1KB數(shù)據(jù)。client端發(fā)送數(shù)據(jù)的速度是要快于server端接收數(shù)據(jù)的速度的,所以client端會(huì)發(fā)送多條數(shù)據(jù),其中每條數(shù)據(jù)為1KB(由server端的mss指定最大接收長度),總共發(fā)送的數(shù)據(jù)不能超過6KB(server端的滑動(dòng)窗口大小)。所以上圖中client端總共發(fā)送了6條數(shù)據(jù),每條數(shù)據(jù)長度為1KB(可見圖中序號2-9數(shù)據(jù)傳輸),正好大小總共為6KB,等于server端窗口大小(實(shí)際上是server端緩沖區(qū)的大小)。
此時(shí),server端的緩沖區(qū)已經(jīng)滿了,不能再接收數(shù)據(jù)了,只有當(dāng)server端把數(shù)據(jù)讀出的時(shí)候,緩沖區(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ù)的時(shí)候,server再次向client發(fā)送一條數(shù)據(jù) ACK,6145, win 4096,這里ACK和確認(rèn)序號不變,win所代表的窗口大小發(fā)生了變化,變成了當(dāng)前空余的緩沖區(qū)大小4KB,可見上圖中序號11所代表的數(shù)據(jù)傳輸,此時(shí)在server發(fā)送數(shù)據(jù)10和11之間,client端并沒有發(fā)送數(shù)據(jù),但是server端要向client端告知自己的空閑緩沖區(qū)大小。
最后,12-18的過程實(shí)際上就是四次揮手的過程,client發(fā)送FIN斷開連接,此時(shí)server還沒有處理完緩沖區(qū)中的數(shù)據(jù),所以每處理一批數(shù)據(jù)都會(huì)向client發(fā)送一條信息并告知緩沖區(qū)剩余大小,直到緩沖區(qū)中的數(shù)據(jù)全部處理完畢,server向client發(fā)送FIN斷開連接。實(shí)際上滑動(dòng)窗口就是緩沖區(qū)的大小,并且在發(fā)送數(shù)據(jù)過程中,并不是client發(fā)一條server就必須收一條,也可以發(fā)多條收多條,這是因?yàn)門CP是流式傳輸,一端發(fā)送的數(shù)據(jù)雖然沒有立即被處理,但是已經(jīng)存起來了,就存在了滑動(dòng)窗口所表示的緩沖區(qū)中,當(dāng)緩沖區(qū)滿了,發(fā)送端就會(huì)臨時(shí)阻塞,等待接收端緩沖區(qū)出現(xiàn)剩余空間。
上面所描述的過程都是所有數(shù)據(jù)傳輸都成功的前提下進(jìn)行的,實(shí)際上,每條數(shù)據(jù)的發(fā)送都可能會(huì)失敗,當(dāng)發(fā)生傳輸失敗的情況,TCP會(huì)進(jìn)行重傳,重傳的示意圖如下:
5. server服務(wù)端與client客戶端編程實(shí)現(xiàn)
server.c
/************************************************************
>File Name : server.c
>Author : Mindtechnist
>Company : Mindtechnist
>Create Time: 2022年08月14日 星期日 19時(shí)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); //使用本機(jī)任意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時(shí)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;
}