通常情況下我們在編寫通訊端通訊程式時都會實現一收一發的通訊模式,當用戶端傳送資料到伺服器端後,我們希望伺服器端處理請求後同樣返回給我們一個狀態值,並以此判斷我們的請求是否被執行成功了,另外增加收發同步有助於避免封包粘包問題的產生,在多數開發場景中我們都會實現該功能。
Socket粘包是指在使用TCP協定傳輸資料時,傳送方連續向接收方傳送多個封包時,接收方可能會將它們合併成一個或多個大的封包,而不是按照傳送方傳送的原始封包拆分成多個小的封包進行接收。
造成粘包的原因主要有以下幾個方面:
如果讀者是一名Windows
平臺開發人員並從事過網路通訊端開發,那麼一定很清楚此缺陷的產生,當我們連續呼叫send()
時就會產生粘包現象,而解決此類方法的最好辦法是在每次send()
後呼叫一次recv()
函數接收一個返回值,至此由於封包不連續則也就不會產生粘包的現象。
伺服器端我們實現的功能只有一個接收,其中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;
}
對於使用者端而言,其與伺服器端保持一致,只需要封裝一個對等的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 許可協定。轉載請註明出處!