目錄
UDP協議
1.1通信流程
1.2函數?
socket
bind
?sendto
recvfrom
close?
1.3實現udp通信
TCP協議
1.1TCP頭部結構
1.2通信流程
三次握手
正式通信
四次揮手
1.3協議特性
面向字節流
可靠傳輸
序列號和確認號
重傳機制
流量控制和擁塞控制
1.4常用函數
socket
bind
listen
accept
connect
send
recv
close?
1.5實現tcp通信
socket又稱套接字,他的存在是為了解決:
- 不同協議的識別TCP?UDP
- 不同主機的識別(哪個IP發 哪個IP收)
- 不同進程的識別(哪個端口發 哪個端口收)?
UDP協議
????????UDP(user datagram protocol)的中文叫用戶數據協議報,屬于傳輸層。UDP是面向非連接的協議,它不與對方建立連接,而是直接把我要發的數據報發給對方。所以UDP適用于一次傳輸數據量很少、對可靠性要求不高的或對實時性要求高的應用場景。
1.1通信流程
udp通信直接傳輸數據,不關心數據是否丟包不會進行重傳。
1.2函數?
socket
#include <sys/socket.h>
int socket(int family,int type,int protocol);
- 功能:創建一個用于網絡通信的socket套接字
- family:協議族(AF_INET(IPv4)、AF_INET6(IPv6)、PF_PACKET(鏈路層編程))
- type:套接字類(SOCK_STREAM(流式套接字)、SOCK_DGRAM(數據報式套接字)SOCK_RAW(原始套接字))
- protocol:協議類別(0、IPPROTO_TCP、IPPROTO_UDP)一般置0
- 返回值:套接字
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- 功能:將socket與本機上的一個端口綁定,隨后就可以在該端口監聽服務請求
- sockfd:正在監聽端口的套接口文件描述符,通過socket獲得
- addr:需要綁定的IP和端口
- addrlen:addr的結構體的大小
- 返回值:失敗返回一個小于0的值,成功返回大于等于0的值
一般只有服務器端才需要“顯示”綁定端口;client的端口號一般由client的OS隨機選擇,client不需要“顯示”地綁定IP和端口號,當client第一次給服務器發消息時OS會自動綁定IP和端口號。
struct sockaddr
{sa_family_t sa_family; // 2字節char sa_data[14] //14字節
};
- struct sockaddr_in:IPv4地址結構
- struct sockaddr:通用地址結構
_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);//填服務器端的信息
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_localport);
local.sin_addr.s_addr = INADDR_ANY;//將套接字與服務器端信息綁定
int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
?sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
? ? ? ? ? ? ? const struct sockaddr *dest_addr, socklen_t addrlen);
- 功能:發送數據、
- sockfd:正在監聽端口的套接字文件描述符,通過socket獲得。
- buf:發送緩沖區,往往是使用者定義的數組,該數組裝有要發送的數據
- len:發送緩沖區的大小,單位是字節
- flags:填0即可
- dest_addr:指向接收數據的主機地址信息的結構體,也就是該參數指定數據要發送到哪個主機哪個進程
- addrlen:表示第五個參數所指向內容的長度
- 返回值:成功返回發送數據長度,失敗返回-1。
recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
? ? ? ? ? ? ? ? struct sockaddr *src_addr, socklen_t *addrlen);
- sockfd:正在監聽端口的套接口文件描述符,通過socket獲得
- buf:接收緩沖區,往往是使用者定義的數組,該數組裝有接收到的數據
- len:接收緩沖區的大小,單位是字節
- flags:一般置0
- src_addr:指向發送數據的主機地址信息的結構體,也就是我們可以從該參數獲取到數據是誰發出的
- addrlen:表示第五個參數所指向內容的長度
- 返回值:成功返回接收成功的數據長度,失敗返回-1
close?
#include <unistd.h>
int close(int fd);
- 功能:關閉某個文件描述符
- fd:socket產生的fd(返回值)
1.3實現udp通信
udp通信的簡單實現:Linux/udp_echo_server · swi/c++ - 碼云 - 開源中國
TCP協議
????????TCP是面向連接的協議,這是因為在一個應用進程可以開始向另一個應用進程發送數據之前,這兩個進程必須先相互“握手”,即它們必須相互發送某些預備報文段,以建立確保數據傳輸的參數。它有以下幾個特點:
- 面向連接:TCP一定是“一對一”的,無法像 UDP 協議那樣在同一時刻像多個主機發送消息,即無法做到一對多;
- 可靠的:無論的網絡鏈路中出現了怎樣的鏈路變化,TCP 都可以保證一個報文一定能夠到達接收端(當然不是說絕對可靠);
- 基于字節流:消息是“沒有邊界”的,所以無論我們消息有多大都可以進行傳輸。并且消息是“有序的”,當前一個消息沒有收到的時候,即使它先收到了后面的字節已經收到,那么也不能扔給應用層去處理,同時對重復的報文會自動丟棄。
1.1TCP頭部結構
?
- 序列號:在連接建立時由計算機計算出的初始值,通過 SYN 包傳給對端主機,每發送一次新的數據包,就累加一次該序列號的大小。用來解決網絡包亂序問題
- 確認應答號:指下次期望收到的數據的序列號,發送端收到這個確認應答以后可以確認確認應答號減一的數據包已經被正常接收。主要用來解決不丟包的問
- ACK:用以指示確認字段中的值是有效的,即該報文段包括一個對已被成功接收的報文段的確認
- RST:用以指示連接的強制拆除,當接收到錯誤連接時會發送RST位置為1的報文
- SYN:用以指示連接的建立,該位為1的報文表示希望建立連接
- FIN:用以指示連接的終止,該位為1的報文表示希望斷開連接
1.2通信流程
三次握手
正式通信
?建立連接過程:
????????服務端創建完監聽套接字使用accept等待用戶連接,當用戶連接時監聽套接字會起到"接客"的作用,此時再創建一個新的套接字來進行服務,而監聽套接字繼續等待新的用戶。用于服務的套接字由accept函數返回。
四次揮手
????????最開始的時候,客戶端和服務器都是處于ESTABLISHED狀態,然后客戶端主動關閉,服務器被動關閉。
- 第一次揮手 客戶端發出連接釋放報文,并且停止發送數據。此時客戶端進入FIN-WAIT-1(終止等待1)狀態。
- 第二次揮手 服務器端接收到連接釋放報文后,發出確認報文(應答)。此時服務端就進入了CLOSE-WAIT 關閉等待狀態。
- 第三次揮手 客戶端接收到服務器端的確認請求后,客戶端就會進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文,服務器將數據發送完畢后,就向客戶端發送連接釋放報文,服務器就進入了LAST-ACK(最后確認)狀態,等待客戶端的確認。
- 第四次揮手 客戶端收到服務器的連接釋放報文后,發出應答此時,此時客戶端就進入了TIME-WAIT(時間等待)狀態,但此時TCP連接還未終止,必須要經過2MSL后(等待最長報文壽命時間,確保發送的應答被服務端收到),當客戶端撤銷相應的TCB后,客戶端才會進入CLOSED關閉狀態,服務器端接收到確認報文后,會立即進入CLOSED關閉狀態,到這里TCP連接就斷開了,四次揮手完成。
1.3協議特性
面向字節流
TCP以字節流的方式傳輸數據,沒有消息邊界,需要應用層進行數據的分包和組包12。這種方式使得TCP能夠靈活地處理各種長度的數據,但也可能導致黏包問題。黏包問題可以通過以下方法解決:
-
數據定長:規定數據的長度。
-
特殊字符進行間隔:使用特殊字符分隔數據。
-
在不定長數據的應用層頭部中添加數據長度字段:接收方根據頭部長度,接收該長度的數據。
可靠傳輸
TCP提供可靠的數據傳輸服務,能夠在數據傳輸過程中檢測和糾正錯誤,確保數據的完整性、順序性和可靠性。TCP通過以下機制確保數據的可靠傳輸:
-
序列號和確認號:每個數據包都有一個序列號,接收方通過確認號告知發送方哪些數據已成功接收。
-
重傳機制:如果發送方未收到確認,會重新發送數據包。
-
流量控制:通過滑動窗口機制,動態調整發送速率,避免接收方緩沖區溢出。
-
擁塞控制:通過慢啟動、擁塞避免等算法,動態調整發送速率,避免網絡擁塞。
序列號和確認號
序列號(seq)
????????序列號的主要作用是跟蹤每個數據包中的數據在整個數據流中的位置。當TCP連接建立時,每一端都會隨機選擇一個初始序列號(ISN),之后傳輸的數據包的序列號將基于這個初始值遞增。例如,如果一個數據包的序列號是100,且包含100字節的數據,那么下一個數據包的序列號將從200開始。這樣,即使數據包在傳輸過程中到達順序被打亂,接收端也能根據序列號重新組裝數據,確保數據的順序性和完整性。
確認號(ack)
????????確認號則是接收端用來告訴發送端哪些數據已經被成功接收的。當接收端收到數據后,它會發送一個確認包,其中包含的確認號是接收到的數據的序列號加1。發送端通過檢查這個確認號來確定數據是否需要重傳。例如,如果發送端收到的確認號是101,那么它知道序列號為100的數據包已被接收端成功接收。
?應答的情況有以下兩種:
?
重傳機制
超時重傳
????????當TCP發送一個數據包后,它會啟動一個定時器等待接收方的確認應答(ACK)。如果在定時器到期之前沒有收到ACK,TCP會認為數據包可能已丟失,并重新發送該數據包。這個過程稱為超時重傳。超時重傳時間(RTO)的設置非常重要,它應該略大于數據包往返時間(RTT)。
快速重傳
????????快速重傳是另一種重傳機制,它不依賴于定時器,而是基于接收到的重復ACK信號。如果發送方連續收到三個相同的ACK,它會立即重傳那些被認為丟失的數據包,而不需要等待定時器超時。
超時重傳:
(數據丟包)
(應答丟包)?
?為什么要等待一個特定時間?
? ? ? ? 如果該等待時間過長會導致網絡空隙時間增大,降低網絡傳輸效率。如果等待時間過短,可能會在應答未到來時進行重傳,導致網絡負荷增大。
?快速重傳:
流量控制和擁塞控制
流量控制:
作用:為了解決發送方和接收方發送和接收能力不匹配而導致的數據丟失問題,當發送方發送的太快,接收方來不及接受就會導致數據丟失;
方式:由接收端采用滑動窗口的形式,告知發送方允許/停止發包解決TCP丟包問題。
擁塞控制:
作用:為了解決過多的數據注入到網絡導致網絡崩潰和超負荷問題;
方式:由發送方采用擁塞窗口的形式去判斷網絡狀態,從而采取不同算法執行TCP動態發包解決網絡整體質量問題。
慢啟動算法:
????????發送方先探測網絡擁塞程度,并不是一開始就發送大量的數據,發送方會根據擁塞程度增大擁塞窗口。(例如:發2個數據段得到應答,再去嘗試發4個數據段...直到找到合適數量的數據段)
1.4常用函數
socket
#include <sys/socket.h>
int socket(int family,int type,int protocol);
- 功能:創建一個用于網絡通信的socket套接字
- family:協議族(AF_INET(IPv4)、AF_INET6(IPv6)、PF_PACKET(鏈路層編程))
- type:套接字類(SOCK_STREAM(流式套接字)、SOCK_DGRAM(數據報式套接字)SOCK_RAW(原始套接字))
- protocol:協議類別(0、IPPROTO_TCP、IPPROTO_UDP)一般置0
- 返回值:套接字
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
- 功能:將socket與本機上的一個端口綁定,隨后就可以在該端口監聽服務請求
- sockfd:正在監聽端口的套接口文件描述符,通過socket獲得
- addr:需要綁定的IP和端口
- addrlen:addr的結構體的大小
- 返回值:失敗返回一個小于0的值,成功返回大于等于0的值
listen
#include <sys/types.h>
#include <sys/socket.h>int listen(int sockfd,int backlog)
- 功能:listen函數使socket處于被動的監聽模式,并為該socket建立一個輸入數據隊列,將到達的服務請求保存在此隊列中,直到程序處理它們。
- sockfd:傳入bind的sockfd。
- backlog:請求連接隊列的最大長度,這個隊列指的是多個客戶端都請求建立連接時,會把這些連接暫時存入隊列中,以便下一步調用accept接受連接。
- 返回值:成功則返回0,否則-1。
accept
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen);
- 功能:讓服務器接受客戶的連接請求。
- sockfd:經過bind和listen的sockfd。
- addr:此處addr參數是傳出的,函數成功執行后,存的是客戶端的IP和端口。
- addrlen:addr的結構體的大小
- 返回值:如果成功,則會返回一個非負整數,也就是accepted socket的文件描述符,后續的通信則用這個文件描述符,如果失敗則返回-1。
connect
#include <sys/types.h>
#include <sys/socket.h>int connect(int sockfd, struct sockaddr* addr, socklen_t addrlen);
- 功能:讓服務器接受客戶的連接請求。
- sockfd:由socket函數創建的文件描述符。
- addr:保存服務端的IP和端口
- addrlen:addr的結構體的大小
- 返回值:0表示成功,-1表示錯誤失敗。
send
#include <sys/types.h> ?
?#include <sys/socket.h>
int send(?SOCKET?s,????const char FAR *buf,????int len,????int flags );??
- 功能:數據發送函數。
- s:指定發送端套接字描述符。
- buf:指明一個存放應用程序要發送數據的緩沖區。
- len:要發送的數據的字節數。
- flags:一般置0。
- 返回值:n(n大于0意為成功發送 n 個字節;n等于0意為對端關閉連接;n小于0意為出錯或者被信號中斷或者對端 TCP 窗口太小數據發不出去(send)或者當前網卡緩沖區已無數據可收(recv))
recv
#include <sys/types.h> ?
?#include <sys/socket.h>
int recv(int s, void *buf, int len, unsigned int flags);
- 功能:數據接收函數。
- s:指定接收端套接字描述符
- buf:指向一個緩沖區,用于存放接收到的數據。
- len:緩沖區的長度。
- flags:一般置0。
- 返回值:n(n大于0意為成功接收?n 個字節;n等于0意為對端關閉連接;n小于0意為出錯)
close?
#include <unistd.h>
int close(int fd);
- 功能:關閉某個文件描述符
- fd:socket產生的fd(返回值)
1.5實現tcp通信
tcp通信的簡單實現:Linux/tcp_echo_server · swi/c++ - 碼云 - 開源中國