通常情況下我們在編寫套接字通信程式時都會實現一收一發的通信模式,當客戶端發送數據到服務端後,我們希望服務端處理請求後同樣返回給我們一個狀態值,並以此判斷我們的請求是否被執行成功了,另外增加收發同步有助於避免數據包粘包問題的產生,在多數開發場景中我們都會實現該功能。Socket粘包是指在使用TCP協議... ...
通常情況下我們在編寫套接字通信程式時都會實現一收一發的通信模式,當客戶端發送數據到服務端後,我們希望服務端處理請求後同樣返回給我們一個狀態值,並以此判斷我們的請求是否被執行成功了,另外增加收發同步有助於避免數據包粘包問題的產生,在多數開發場景中我們都會實現該功能。
Socket粘包是指在使用TCP協議傳輸數據時,發送方連續向接收方發送多個數據包時,接收方可能會將它們合併成一個或多個大的數據包,而不是按照發送方發送的原始數據包拆分成多個小的數據包進行接收。
造成粘包的原因主要有以下幾個方面:
- TCP協議的特性:TCP是一種面向連接的可靠傳輸協議,保證了數據的正確性和可靠性。在TCP協議中,發送方和接收方之間建立了一條虛擬的連接,通過三次握手來建立連接。當數據在傳輸過程中出現丟失、損壞或延遲等問題時,TCP會自動進行重傳、校驗等處理,這些處理會導致接收方在接收數據時可能會一次性接收多個數據包。
- 緩衝區的大小限制:在接收方的緩衝區大小有限的情況下,如果發送方發送的多個小數據包的總大小超過了接收方緩衝區的大小,接收方可能會將它們合併成一個大的數據包來接收。
- 數據的處理方式:接收方在處理數據時,可能會使用不同的方式來處理數據,比如按照位元組流方式讀取數據,或者按照固定長度讀取數據等方式。不同的處理方式可能會導致接收方將多個數據包合併成一個大的數據包。
如果讀者是一名Windows
平臺開發人員並從事過網路套接字開發,那麼一定很清楚此缺陷的產生,當我們連續調用send()
時就會產生粘包現象,而解決此類方法的最好辦法是在每次send()
後調用一次recv()
函數接收一個返回值,至此由於數據包不連續則也就不會產生粘包的現象。
14.8.1 服務端實現
服務端我們實現的功能只有一個接收,其中RecvFunction
函數主要用於接收數據包,通過使用recv
函數接收來自socket
連接通道的數據,並根據接收到的數據判斷條件,決定是否發送數據回應。如果接收到的數據中命令參數滿足command_int_a=10
和command_int_b=20
,那麼該函數會構建一個新的數據包,將其發送回客戶端,其中包括一個表示成功執行的標誌、一個包含歡迎信息的字元串以及其他數據信息。如果接收到的數據命令參數不滿足上述條件,則函數會構建一個新的數據包,將其發送回客戶端,其中只包括一個表示執行失敗的標誌。最後,函數返回一個BOOL
類型的布爾值,表示接收函數是否成功執行。
#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
typedef struct
{
int command_int_a;
int command_int_b;
int command_int_c;
int command_int_d;
unsigned int command_uint_a;
unsigned int command_uint_b;
char command_string_a[256];
char command_string_b[256];
char command_string_c[256];
char command_string_d[256];
int flag;
int count;
}send_recv_struct;
// 調用接收函數
BOOL RecvFunction(SOCKET &sock)
{
// 接收數據
char recv_buffer[8192] = { 0 };
int recv_flag = recv(sock, (char *)&recv_buffer, sizeof(send_recv_struct), 0);
if (recv_flag <= 0)
{
return FALSE;
}
send_recv_struct *buffer = (send_recv_struct *)recv_buffer;
std::cout << "接收參數A: " << buffer->command_int_a << std::endl;
// 接收後判斷,判斷後發送標誌或攜帶參數
if (buffer->command_int_a == 10 && buffer->command_int_b == 20)
{
send_recv_struct send_buffer = { 0 };
send_buffer.flag = 1;
strcpy(send_buffer.command_string_a, "hello lyshark");
// 發送數據
int send_flag = send(sock, (char *)&send_buffer, sizeof(send_recv_struct), 0);
if (send_flag <= 0)
{
return FALSE;
}
}
else
{
send_recv_struct send_buffer = { 0 };
send_buffer.flag = 0;
// 發送數據
int send_flag = send(sock, (char *)&send_buffer, sizeof(send_recv_struct), 0);
if (send_flag <= 0)
{
return FALSE;
}
return FALSE;
}
return TRUE;
}
int main(int argc, char *argv[])
{
WSADATA WSAData;
if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
{
std::cout << "WSA動態庫初始化失敗" << std::endl;
return 0;
}
SOCKET server_socket;
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == ERROR)
{
std::cout << "Socket 創建失敗" << std::endl;
WSACleanup();
return 0;
}
struct sockaddr_in ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(9999);
ServerAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(server_socket, (LPSOCKADDR)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
{
std::cout << "綁定套接字失敗" << std::endl;
closesocket(server_socket);
WSACleanup();
return 0;
}
if (listen(server_socket, 10) == SOCKET_ERROR)
{
std::cout << "偵聽套接字失敗" << std::endl;
closesocket(server_socket);
WSACleanup();
return 0;
}
SOCKET message_socket;
char buf[8192] = { 0 };
if ((message_socket = accept(server_socket, (LPSOCKADDR)0, (int*)0)) == INVALID_SOCKET)
{
return 0;
}
send_recv_struct recv_buffer = { 0 };
// 接收對端數據到recv_buffer
BOOL flag = RecvFunction(message_socket);
std::cout << "接收狀態: " << flag << std::endl;
closesocket(message_socket);
closesocket(server_socket);
WSACleanup();
return 0;
}
14.8.2 客戶端實現
對於客戶端而言,其與服務端保持一致,只需要封裝一個對等的SendFunction
函數,該函數使用send
函數將一個send_recv_struct
類型的指針send_ptr
發送到指定的socket
連接通道。在發送完成後,函數使用recv
函數從socket
連接通道接收數據,並將其存儲到一個char
型數組recv_buffer
中。接下來,該函數使用send_recv_struct
類型的指針buffer
將該char
型數組中的數據複製到一個新的send_recv_struct
類型的結構體變數recv_ptr
中,最後返回一個BOOL
類型的布爾值,表示發送接收函數是否成功執行。
#include <iostream>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
typedef struct
{
int command_int_a;
int command_int_b;
int command_int_c;
int command_int_d;
unsigned int command_uint_a;
unsigned int command_uint_b;
char command_string_a[256];
char command_string_b[256];
char command_string_c[256];
char command_string_d[256];
int flag;
int count;
}send_recv_struct;
// 調用發送接收函數
BOOL SendFunction(SOCKET &sock, send_recv_struct &send_ptr, send_recv_struct &recv_ptr)
{
// 發送數據
int send_flag = send(sock, (char *)&send_ptr, sizeof(send_recv_struct), 0);
if (send_flag <= 0)
{
return FALSE;
}
// 接收數據
char recv_buffer[8192] = { 0 };
int recv_flag = recv(sock, (char *)&recv_buffer, sizeof(send_recv_struct), 0);
if (recv_flag <= 0)
{
return FALSE;
}
send_recv_struct *buffer = (send_recv_struct *)recv_buffer;
memcpy((void *)&recv_ptr, buffer, sizeof(send_recv_struct));
return TRUE;
}
int main(int argc, char* argv[])
{
WSADATA WSAData;
if (WSAStartup(MAKEWORD(2, 0), &WSAData) == SOCKET_ERROR)
{
return 0;
}
SOCKET client_socket;
if ((client_socket = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR)
{
WSACleanup();
return 0;
}
struct sockaddr_in ClientAddr;
ClientAddr.sin_family = AF_INET;
ClientAddr.sin_port = htons(9999);
ClientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(client_socket, (LPSOCKADDR)&ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
{
closesocket(client_socket);
WSACleanup();
return 0;
}
send_recv_struct send_buffer = {0};
send_recv_struct response_buffer = { 0 };
// 填充發送數據包
send_buffer.command_int_a = 10;
send_buffer.command_int_b = 20;
send_buffer.flag = 0;
// 發送數據包,並接收返回結果
BOOL flag = SendFunction(client_socket, send_buffer, response_buffer);
if (flag == FALSE)
{
return 0;
}
std::cout << "響應狀態: " << response_buffer.flag << std::endl;
if (response_buffer.flag == 1)
{
std::cout << "響應數據: " << response_buffer.command_string_a << std::endl;
}
closesocket(client_socket);
WSACleanup();
return 0;
}
運行上述代碼片段,讀者可看到如下圖所示的輸出信息;
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/4796bde3.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17768108.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!