1. TCP三次握手
TCP是一種面向連接的安全的流式傳輸協(xié)議,TCP報文的格式如下
標志位URG、ACK、PSH、PST、SYN、FIN
SYN:請求建立連接
ACK:給對端應答
FIN:斷開連接
16位窗口大?。哼@里的窗口實際上就是滑動窗口(將在后面介紹),這個窗口大小只是記錄了存放數(shù)據(jù)的緩沖區(qū)也就是窗口有多大,而不是實際存放數(shù)據(jù)的地方。
32位序號:在請求建立連接時跟在SYN標志位后面的隨機序號。
32位確認序號:在應答時跟在ACK標志位后面的確認序號,它的值是對端在建立連接時跟隨在對端發(fā)送的SYN后面的隨機序號的值加上SYN攜帶的數(shù)據(jù)大小再加上1,如果對端SYN未攜帶數(shù)據(jù),則直接在對端SYN后面隨機序號的基礎上加1。比如客戶端發(fā)起連接并攜帶SYN 100(10),服務端收到后會回復ACK 111,表示連接請求已收到且10個字節(jié)的數(shù)據(jù)也已收到;如果客戶端發(fā)起的連接為SYN 100,不攜帶數(shù)據(jù),那么服務端回復為ACK 101??傊珹CK后面的確認序號值為對端SYN隨機序號+攜帶數(shù)據(jù)大小+1。
TCP在建立連接的時候需要進行三次握手(TCP握手時一定有SYN標志,不帶SYN標志的為建立連接后的正常數(shù)據(jù)傳輸)
第一次握手:第一次握手應該由客服端發(fā)起,服務端被動等待連接。
客戶端:主動發(fā)起連接
攜帶標志位SYN
產(chǎn)生32位隨機序號:比如100
可以攜帶數(shù)據(jù),也可以不攜帶數(shù)據(jù),攜帶數(shù)據(jù)時要帶上數(shù)據(jù)大小比如10字節(jié) SYN 100(10),不攜帶數(shù)據(jù)為SYN 100。
服務端:
檢測SYN的值是否為1。如果SYN值為0,則握手失??;如果SYN值為1,則握手成功,服務端回復,進行第二次握手。
第二次握手:服務端回復并請求建立反向連接。
服務端:服務端發(fā)送確認信號ACK并攜帶一個32位的確認序號,確認序號的值為客戶端SYN隨機序號的值+1,加的這個1實際上是SYN標志的大小1字節(jié)。同時,服務端向客戶端發(fā)起一個反向的連接請求SYN,并攜帶一個32為隨機序號。
確認應答:ACK標志位+32位確認序號(值為客戶端隨機序號的值+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)
客戶端:檢測確認標志ACK是否為1,并校驗確認序號是否正確。
檢測ACK:為1,對端收到并確認應答。
校驗:不攜帶數(shù)據(jù)時,確認序號為101,正好為自己發(fā)送的SYN100的值加1;攜帶數(shù)據(jù)時,確認序號為111,說明自己發(fā)送的10字節(jié)數(shù)據(jù)對端已收到。
第三次握手:客戶端向服務端發(fā)送確認數(shù)據(jù)包,完成反向連接的建立
客戶端:確認應答,ACK+確認信號
服務端未攜帶數(shù)據(jù):ACK 201
服務端攜帶數(shù)據(jù):ACK 211
服務端:
檢測ACK是否為1
校驗確認序號是否正確
至此,三次握手成功,雙向連接均已建立,可以開始數(shù)據(jù)傳輸了。示意圖如下:
2. TCP四次揮手
TCP斷開連接時需要進行四次揮手:
客戶端與服務端哪一端主動斷開連接都可以;
揮手時需要一個標志位FIN,F(xiàn)IN后面也需要跟一個序號,序號的值為對端最后一次發(fā)送的ACK后面的確認序號;
四次揮手的過程如下:
第一次揮手:某一端主動發(fā)起斷開連接的請求。
客戶端:發(fā)送斷開連接的請求
FIN + 序號(對端最后一個ACK后面的確認序號)
ACK + 序號(自己上一次ACK后面的確認序號)
第二次揮手:另一端確認斷開連接。
服務端:
檢測FIN的值是否為1,如果不是1則揮手失敗,即斷開連接失??;
ACK + 序號(對端FIN后面的序號+收到數(shù)據(jù)的大小+1),告訴對端對方發(fā)送的數(shù)據(jù)自己收到了多少;
第三次揮手:另一端請求斷開反向連接(TCP是雙向連接,經(jīng)過前兩次揮手只斷開了單向連接,所以需要反向斷開連接)。
服務端:發(fā)送反向斷開連接的請求。
FIN + 序號(客戶端最后一次ACK所攜帶的確認序號);
ACK + 序號(自己上一次ACK后面的確認序號)
第四次揮手:TCP雙向連接斷開。
客戶端:客戶端對服務端發(fā)送的斷開連接請求進行確認。
ACK + 序號(對端FIN后面的序號+收到數(shù)據(jù)的大小+1)
3. TCP連接與數(shù)據(jù)傳輸過程
首先看一個TCP連接與數(shù)據(jù)傳輸?shù)氖疽鈭D
在圖中:
- 1-3:三次握手階段(握手階段一定有SYN標志)
- 4-6:數(shù)據(jù)傳輸階段
- 7-10:四次揮手階段(揮手階段一定有FIN標志)
圖中的<mss 1460>表示最大數(shù)據(jù)長度,即告知對端給我發(fā)送數(shù)據(jù)的時候不要超過這個最大長度。
詳細分析上述過程的完整圖示如下
4. TCP滑動窗口機制
首先看滑動窗口的示意圖
在圖中,發(fā)送端速度快,接收端速度慢,一般來說誰先發(fā)送SYN誰就是客戶端,因為客戶端總是主動連接服務端,而服務端則被動等待客戶端的連接。
在TCP中,滑動窗口實際上就是一塊緩沖區(qū)(緩存)。在上圖中,客戶端與服務端進行數(shù)據(jù)傳輸?shù)臅r候總是帶有一個win 4096或win 6144等標志,這個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> 可知,服務端滑動窗口大小為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ù)了,只有當server端把數(shù)據(jù)讀出的時候,緩沖區(qū)才能空出位置接受新數(shù)據(jù)。當server端讀取了2K數(shù)據(jù),那么server端的緩沖區(qū)將空余出2K字節(jié),請見上圖中的序號10處,server端向client端回復 ACK,6145, win 2048,表示server端收到了6144字節(jié)(6KB)數(shù)據(jù),6145是由6144+1來的,win 2048表示server端現(xiàn)在緩沖區(qū)空余2K空間。當server端再次讀出2K數(shù)據(jù)的時候,server再次向client發(fā)送一條數(shù)據(jù) ACK,6145, win 4096,這里ACK和確認序號不變,win所代表的窗口大小發(fā)生了變化,變成了當前空余的緩沖區(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ū)中,當緩沖區(qū)滿了,發(fā)送端就會臨時阻塞,等待接收端緩沖區(qū)出現(xiàn)剩余空間。
上面所描述的過程都是所有數(shù)據(jù)傳輸都成功的前提下進行的,實際上,每條數(shù)據(jù)的發(fā)送都可能會失敗,當發(fā)生傳輸失敗的情況,TCP會進行重傳,重傳的示意圖如下:
5. server服務端與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);
?}
?//設置監(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);
?}
?//連接服務器
?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ā)送給服務器
? ?write(fd, buf, strlen(buf));
? ?//接收服務端數(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;
}