tcp特點+TCP的狀態轉換圖+time wait詳解
目錄
一、tcp特點解釋
1.1 面向連接
1.1.1 連接建立——三次握手
1.1.2 連接釋放——四次揮手
1.2 可靠的
1.2.1 應答確認
1.2.2 超時重傳
1.2.3 亂序重排
1.2.4 去重
1.2.5 滑動窗口進行流量控制
1.3 流失服務(字節流傳輸)
1.3.1 tcp粘包概念
1.3.2?產生 TCP 粘包的原因
發送端原因
接收端原因
1.3.3?解決 TCP 粘包的方法
定長協議
分隔符協議
消息頭 + 消息體協議
二、服務器接收多個客戶端?
三、TCP(傳輸控制協議)的狀態轉換圖
2.1 狀態
2.2 轉換
一、tcp特點解釋
1.1 面向連接
1.1.1 連接建立——三次握手
在進行數據傳輸之前,TCP 需要通過 “三次握手” 來建立連接。具體過程為:客戶端向服務器發送一個 SYN 包,請求建立連接;服務器收到 SYN 包后,向客戶端發送一個 SYN + ACK 包,表示同意建立連接;客戶端收到 SYN + ACK 包后,再向服務器發送一個 ACK 包,連接建立完成。這種方式確保了雙方都有發送和接收數據的能力,并且雙方對連接的初始序列號達成一致。
1.1.2 連接釋放——四次揮手
數據傳輸結束后,TCP 使用 “四次揮手” 來釋放連接。客戶端發送一個 FIN 包,表示請求關閉連接;服務器收到 FIN 包后,發送一個 ACK 包表示同意關閉;接著服務器發送一個 FIN 包,表示自己也請求關閉連接;客戶端收到 FIN 包后,發送一個 ACK 包表示同意關閉,連接釋放完成。這種機制保證了雙方都能正確地結束數據傳輸。
1.2 可靠的
1.2.1 應答確認
TCP 使用確認機制來確保數據的可靠傳輸。發送方發送數據后,會等待接收方的確認信息(ACK)。如果在一定時間內沒有收到確認信息,發送方會重新發送該數據。例如,發送方發送了一個數據包,接收方收到后會返回一個帶有確認號的 ACK 包,告知發送方已經正確接收了哪些數據。
1.2.2 超時重傳
當發送方發送的數據丟失或者接收方返回的確認信息丟失時,發送方會在超時后重傳數據。TCP 通過設置定時器來實現超時重傳,定時器的時間會根據網絡狀況動態調整。
1.2.3 亂序重排
我們每發一個tcp報文都有相應的序號。TCP 保證字節流中的數據按照發送的順序到達接收方。如果數據在傳輸過程中出現亂序,TCP 會在接收方進行重新排序,確保應用層接收到的數據是有序的
1.2.4 去重
倆個相同序號的報文去重
1.2.5 滑動窗口進行流量控制
滑動窗口用于控制數據的發送速率和流量,同時保證數據的可靠傳輸。發送方和接收方都有一個滑動窗口,窗口的大小表示可以發送或接收的數據量。滑動窗口越大代表我能發送的數據越大.發送方在發送數據時,會根據接收方的窗口大小來決定發送多少數據,避免接收方緩沖區溢出。窗口內的允許發送,窗口外的不允許發送
(不會丟包因為是可靠的,底層可能會丟但是會重傳時間特別快,我們應用層感受不到)
1.3 流失服務(字節流傳輸)
多線程并發——給一個服務器同時鏈接倆個以上客戶端
發送緩沖區:send數據寫到這里
接收緩沖區:recv接收數據
TCP 將應用層的數據看作是無邊界的字節流進行傳輸。發送方可以將多個應用層的消息合并成一個字節流發送,接收方需要自己從字節流中提取出各個消息。例如,應用層發送了兩條消息 “Hello” 和 “World”,TCP 可能會將它們合并成一個字節流 “HelloWorld” 進行發送,接收方需要根據具體的協議來區分這兩條消息。
這張圖展示了 TCP 字節流服務的特性,主要體現以下幾點:
發送端 - **應用層**:應用層通過多次調用 `send()` 函數分別發送 “hello”、“abc”、“test” 這些數據。`send()` 函數用于將應用層數據傳遞給傳輸層。 - **傳輸層**:TCP 協議將這些數據暫存于 TCP 發送緩沖區。TCP 為了提高傳輸效率,會根據自身機制(如窗口大小、擁塞控制等 )對發送緩沖區中的數據進行組合和封裝。如圖中所示,可能會把 “hello” 和 “abc” 組合在一起封裝成一個 TCP 報文段,“test” 單獨封裝成一個 TCP 報文段。這種組合并非嚴格按應用層發送順序和邊界,而是根據 TCP 自身策略。
接收端 - **傳輸層**:接收端的 TCP 協議從網絡中接收 TCP 報文段,先存儲在 TCP 接收緩沖區。 - **應用層**:應用層通過 `recv()` 函數從 TCP 接收緩沖區讀取數據。由于 TCP 發送時可能對數據進行了組合封裝,接收端應用層調用 `recv()` 讀取數據時,不一定能按發送端應用層的原始邊界和順序獲取數據,可能會一次性讀取多個發送端組合封裝的數據,也可能分多次讀取。 總體而言,該圖展示了 TCP 字節流服務中,數據在發送端和接收端的處理過程,突出 TCP 并不保證應用層數據的邊界,而是以字節流形式進行傳輸和處理。 ?
1.3.1 tcp粘包概念
TCP(Transmission Control Protocol,傳輸控制協議)是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。而 TCP 粘包是在使用 TCP 進行數據傳輸時可能遇到的一個常見問題。
TCP 粘包指的是在 TCP 連接中,發送方發送的若干個數據包,到接收方接收時,這些數據包粘連在一起,接收方難以區分哪些字節屬于哪個原始數據包的現象。?
1.3.2?產生 TCP 粘包的原因
發送端原因
- Nagle 算法:TCP 為了提高傳輸效率,采用了 Nagle 算法。該算法會將小的數據包合并成大的數據包進行發送,以此減少網絡中的數據包數量。例如,當你連續發送多個小數據包時,Nagle 算法可能會將它們合并成一個大的數據包發送,從而造成粘包。
- TCP 緩沖區:TCP 協議的發送緩沖區用于暫存待發送的數據。如果發送方的數據產生速度大于網絡的發送速度,那么數據就會在緩沖區中累積,當緩沖區滿或者達到一定條件時,就會將緩沖區中的數據一起發送出去,這也可能導致多個數據包粘連在一起。
接收端原因
- TCP 接收緩沖區:接收端在接收數據時,會將數據先存放在接收緩沖區中。如果接收方沒有及時從緩沖區中讀取數據,后續的數據也會不斷地存入緩沖區,這樣就可能導致多個數據包的數據混合在一起,形成粘包。
1.3.3?解決 TCP 粘包的方法
定長協議
規定每個數據包的長度是固定的。接收方按照固定長度來讀取數據,這樣就可以明確區分每個數據包。
分隔符協議
在每個數據包的末尾添加一個特殊的分隔符,接收方根據分隔符來區分不同的數據包。
消息頭 + 消息體協議
在每個數據包的前面添加一個消息頭,消息頭中包含消息體的長度信息。接收方先讀取消息頭,根據消息頭中的長度信息來讀取相應長度的消息體。
二、服務器接收多個客戶端 用fork做并發
它是一個簡單的TCP服務器程序,它在本地6000端口上監聽連接請求,接受客戶端的連接,并為每個連接創建一個新的進程來處理
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>int main()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);//創建監聽套接字if( -1 == sockfd ){exit(1);}//定義套接字地址結構, ipv4,ipv5,unixstruct sockaddr_in saddr,caddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//將套接字綁定到指定的IP地址和端口。如果綁定失敗(返回-1),則打印錯誤信息并退出程序。int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//指定ip portif( -1 == res){printf("bind err\n");exit(1);}//使套接字進入監聽狀態,監聽隊列大小設置為5。如果監聽失敗(返回-1),則退出程序。res = listen(sockfd,5);//設置監聽隊列 大小是5if( -1 == res){exit(1);}//進入一個無限循環,等待客戶端的連接請求。accept函數用于接受連接請求,并將客戶端的地址信息存儲在caddr中。while( 1 ){int len = sizeof(caddr);int c = accept(sockfd,(struct sockaddr*)&caddr,&len);//檢查是否成功接受連接。如果失敗,則繼續下一次循環。if( c < 0 ){continue;}//創建一個新的進程來處理這個連接。如果創建進程失敗,則關閉連接并繼續下一次循環pid_t pid = fork();if( pid == -1 ){close(c);continue;}//在子進程中,進入一個無限循環,接收客戶端發送的數據,并發送"ok"作為響應。如果接收失敗或客戶端關閉連接,則打印信息,關閉連接,并退出子進程。if( pid == 0 ){while( 1 ){char buff[128] = {0};int n = recv(c,buff,127,0);if( n<= 0 ){break;}printf("recv:%s\n",buff);send(c,"ok",2,0);}printf("client close\n");close(c);exit(0);}close(c);}
}
這個程序實現了一個簡單的TCP服務器,它可以在本地6000端口上監聽連接請求,接受客戶端的連接,并為每個連接創建一個新的進程來處理。服務器接收客戶端發送的數據,并發送"ok"作為響應。服務器使用多進程模型來處理多個客戶端的連接。?
TCP客戶端程序cli,它連接到本地服務器(127.0.0.1),發送消息,并接收服務器的響應
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>int main()
{//創建一個TCP套接字。AF_INET 表示使用IPv4地址,SOCK_STREAM 表示使用面向連接的流式套接字。int sockfd = socket(AF_INET,SOCK_STREAM,0);//檢查套接字是否創建成功。如果失敗(返回-1),則退出程序。if( sockfd == -1 ){exit(1);}//定義并初始化服務器的地址結構。memset 用于清零結構體,sin_family 設置為IPv4,sin_port 設置為6000端口(使用htons函數轉換為網絡字節序),sin_addr.s_addr 設置為本地地址127.0.0.1(使用inet_addr函數轉換為網絡字節序)。struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");//嘗試連接到服務器。如果連接失敗(返回-1),則打印錯誤信息并退出程序。int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if ( res == -1 ){printf("connect err\n");exit(1);}//進入一個無限循環,提示用戶輸入,然后使用fgets函數從標準輸入讀取一行文本(最多127個字符),存儲在緩沖區buff中。while( 1 ){printf("input\n");char buff[128] = {0};fgets(buff,128,stdin);//檢查用戶輸入是否為"end"。如果是,則退出循環。if( strncmp(buff,"end",3) == 0 ){break;}//發送用戶輸入的消息到服務器(不包括換行符),清空緩沖區,接收服務器的響應,并打印出來。send(sockfd,buff,strlen(buff)-1,0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("read:%s\n",buff);}close(sockfd);exit(0);
}
?這個程序實現了一個簡單的TCP客戶端,可以連接到本地服務器,發送消息,并接收服務器的響應。它使用了基本的套接字編程技術,包括創建套接字、連接服務器、發送和接收數據以及關閉套接字。程序通過一個無限循環來持續接收用戶輸入,并在輸入"end"時退出。
?
這兩段代碼分別實現了一個簡單的 TCP 客戶端和一個 TCP 服務器。
?TCP 服務器代碼解釋
這段代碼實現了一個 TCP 服務器,它在本地計算機的 6000 端口上監聽來自客戶端的連接請求。
1. **創建套接字**:
? ?- 使用 `socket` 函數創建一個 TCP 套接字 `sockfd`。2. **綁定地址**:
? ?- 使用 `bind` 函數將套接字綁定到本地地址 `127.0.0.1` 和端口 `6000` 上。這樣,服務器就準備好在指定的地址和端口上監聽客戶端的連接請求。3. **監聽連接**:
? ?- 使用 `listen` 函數使套接字進入監聽狀態,等待客戶端的連接請求。監聽隊列的大小設置為 5,這意味著服務器可以同時處理最多 5 個未處理的連接請求。4. **接受連接**:
? ?- 使用 `accept` 函數接受客戶端的連接請求。每當有新的客戶端連接時,`accept` 函數會返回一個新的套接字 `c`,用于與該客戶端進行通信。5. **處理客戶端請求**:
? ?- 使用 `fork` 創建一個新的子進程來處理每個客戶端的請求。這樣,服務器可以同時處理多個客戶端的連接。
? ?- 在子進程中,使用 `recv` 函數接收客戶端發送的數據,并使用 `send` 函數發送響應(在這個例子中,響應是字符串 "ok")。
? ?- 當客戶端關閉連接或發生錯誤時,子進程會退出。6. **關閉連接**:
? ?- 在父進程中,關閉與客戶端的連接套接字 `c`。?TCP 客戶端代碼解釋
這段代碼實現了一個 TCP 客戶端,它連接到本地服務器(127.0.0.1:6000),發送消息,并接收服務器的響應。
1. **創建套接字**:
? ?- 使用 `socket` 函數創建一個 TCP 套接字 `sockfd`。2. **連接到服務器**:
? ?- 使用 `connect` 函數連接到服務器的地址 `127.0.0.1` 和端口 `6000` 上。3. **發送和接收數據**:
? ?- 進入一個無限循環,提示用戶輸入消息。
? ?- 使用 `fgets` 函數從標準輸入讀取用戶輸入的消息。
? ?- 如果用戶輸入 "end",則退出循環,關閉連接并退出程序。
? ?- 使用 `send` 函數將用戶輸入的消息發送到服務器。
? ?- 使用 `recv` 函數接收服務器的響應,并使用 `printf` 函數打印響應。4. **關閉連接**:
? ?- 在退出循環后,關閉與服務器的連接套接字 `sockfd`。### 總結
這兩段代碼實現了一個簡單的 TCP 客戶端-服務器通信模型:
- **服務器**:在本地計算機的 6000 端口上監聽客戶端的連接請求,使用多進程模型來處理每個客戶端的請求。服務器接收客戶端發送的數據,并發送 "ok" 作為響應。
- **客戶端**:連接到本地服務器,發送用戶輸入的消息,并接收服務器的響應。這種模型可以用于實現各種基于 TCP 的網絡應用程序,如聊天程序、文件傳輸等。
會出現僵死進程,所以需要加入信號的使用來解決僵死進程
三、TCP(傳輸控制協議)的狀態轉換圖
展示了TCP連接從建立到關閉的整個過程中可能經歷的各種狀態。
2.1 狀態
1. **CLOSED**:初始狀態,表示連接尚未建立。
2. **LISTEN**:服務器在該狀態監聽來自客戶端的連接請求。
3. **SYN_SENT**:客戶端發送SYN請求連接,等待服務器確認。
4. **SYN_RCVD**:服務器收到SYN請求后,發送SYN+ACK響應,進入此狀態,等待客戶端的確認。
5. **ESTABLISHED**:雙方確認連接后,連接建立,可以開始傳輸數據。
6. **FIN_WAIT_1**:主動關閉連接的一方發送FIN請求,希望關閉連接。
7. **FIN_WAIT_2**:在FIN_WAIT_1狀態下收到對方的ACK后,進入此狀態,等待對方的FIN請求。
8. **CLOSING**:雙方同時發送FIN請求,等待對方的ACK。
9. **TIME_WAIT**:主動關閉連接的一方在發送FIN請求并收到對方的ACK后,進入此狀態,等待一段時間以確保對方收到ACK。
10. **CLOSE_WAIT**:被動關閉連接的一方收到FIN請求后,進入此狀態,等待應用程序關閉連接。
11. **LAST_ACK**:被動關閉連接的一方發送FIN請求后,等待對方的ACK。
2.2 轉換
1. **被動打開**:服務器從CLOSED狀態進入LISTEN狀態,等待客戶端的連接請求。
2. **主動打開**:客戶端從CLOSED狀態發送SYN請求,進入SYN_SENT狀態。
3. **連接建立**:
? ?- 客戶端發送SYN請求,進入SYN_SENT狀態。
? ?- 服務器收到SYN請求,發送SYN+ACK響應,進入SYN_RCVD狀態。
? ?- 客戶端收到SYN+ACK響應,發送ACK確認,進入ESTABLISHED狀態。
? ?- 服務器收到ACK確認,進入ESTABLISHED狀態。
4. **數據傳輸**:在ESTABLISHED狀態下,雙方可以進行數據傳輸。
5. **連接關閉**:
? ?- 主動關閉的一方發送FIN請求,進入FIN_WAIT_1狀態。
? ?- 被動關閉的一方收到FIN請求,發送ACK確認,進入CLOSE_WAIT狀態。
? ?- 被動關閉的一方發送FIN請求,進入LAST_ACK狀態。
? ?- 主動關閉的一方收到FIN請求,發送ACK確認,進入TIME_WAIT狀態。
? ?- TIME_WAIT狀態持續一段時間后,連接關閉,進入CLOSED狀態。?
TCP連接的建立和關閉是一個復雜的過程,涉及到多個狀態和狀態轉換。連接建立需要三次握手(SYN, SYN+ACK, ACK),而連接關閉需要四次揮手(FIN, ACK, FIN, ACK)。這種設計確保了連接的可靠性和數據的完整性。
在Linux系統中使用
netstat
命令查看網絡連接狀態的輸出結果。netstat
是一個常用的網絡工具,用于顯示網絡連接、路由表、接口統計等信息。圖片中的命令netstat -nat
用于顯示所有網絡連接和監聽端口,包括TCP和UDP協議,并且不解析服務名稱
輸出字段:
Proto
:協議類型,如TCP或TCP6。
Recv-Q
:接收隊列長度。
Send-Q
:發送隊列長度。
Local Address
:本地地址和端口。
Foreign Address
:遠程地址和端口。
State
:連接狀態,如LISTEN、ESTABLISHED、CLOSE_WAIT等。
PID/Program name
:進程ID和程序名稱。狀態解釋:
LISTEN
:端口正在監聽,等待連接請求。
ESTABLISHED
:連接已建立,數據可以傳輸。
CLOSE_WAIT
:被動關閉連接的一方等待關閉連接。
FIN_WAIT_1
:主動關閉連接的一方等待對方的FIN請求。
FIN_WAIT_2
:主動關閉連接的一方等待對方的ACK確認。
TIME_WAIT
:主動關閉連接的一方等待一段時間以確保對方收到ACK。
第一張圖顯示了多個端口處于LISTEN狀態,表示這些端口正在等待連接請求。
第二張圖顯示了一個端口(127.0.0.1:6000)已經完成了三次握手,處于ESTABLISHED狀態,表示連接已經建立。
第三張圖顯示了一個端口(127.0.0.1:6000)處于CLOSE_WAIT狀態,表示被動關閉連接的一方等待關閉連接。
變成time wait端口被占用 狀態服務器不能啟動
為什么需要time wait狀態??
1. 可靠的終止TCP連接
TCP 連接的終止需要通過四次揮手過程來完成。在主動關閉連接的一方發送了 FIN 報文后,它需要等待對方的 ACK 報文。然而,由于網絡延遲或其他原因,這個 ACK 報文可能會丟失或延遲到達。如果主動關閉方在發送 FIN 報文后立即關閉,那么它將無法接收到這個 ACK 報文,從而導致連接沒有被正確關閉。
為了避免這種情況,主動關閉方會進入
TIME_WAIT
狀態,等待一段時間(通常是 2 倍的 MSL,即 Maximum Segment Lifetime,報文段的最大生存時間)。這段時間足夠長,可以確保即使 ACK 報文丟失或延遲,主動關閉方也能夠接收到來自對方的重傳 ACK 報文,從而可靠地終止連接。2. 保證讓遲來的TCP報文段有足夠的時間被識別并丟棄
TCP 連接的標識是由源 IP 地址、目的 IP 地址、源端口號和目的端口號共同組成的。當一個 TCP 連接被關閉后,這些標識可能會被新的連接所使用。然而,由于網絡延遲或其他原因,舊連接的報文段可能會在連接關閉后仍然存在于網絡中。
如果新的連接使用了相同的標識,那么這些遲來的舊報文段可能會被錯誤地識別為新連接的報文段,從而導致數據混亂。
TIME_WAIT
狀態通過等待一段時間,確保所有舊的報文段都已經被丟棄或過期,從而避免這種情況的發生。