C/C++ socket編程教程之九:TCP的粘包問題以及數據的無邊界性


C/C++ socket編程教程之九:TCP的粘包問題以及數據的無邊界性

上節我們講到了socket緩沖區和數據的傳遞過程,可以看到數據的接收和發送是無關的,read()/recv() 函數不管數據發送了多少次,都會盡可能多的接收數據。也就是說,read()/recv() 和 write()/send() 的執行次數可能不同。

例如,write()/send() 重復執行三次,每次都發送字符串"abc",那么目標機器上的 read()/recv() 可能分三次接收,每次都接收"abc";也可能分兩次接收,第一次接收"abcab",第二次接收"cabc";也可能一次就接收到字符串"abcabcabc"。

假設我們希望客戶端每次發送一位學生的學號,讓服務器端返回該學生的姓名、住址、成績等信息,這時候可能就會出現問題,服務器端不能區分學生的學號。例如第一次發送 1,第二次發送 3,服務器可能當成 13 來處理,返回的信息顯然是錯誤的。

這就是數據的“粘包”問題,客戶端發送的多個數據包被當做一個數據包接收。也稱數據的無邊界性,read()/recv() 函數不知道數據包的開始或結束標志(實際上也沒有任何開始或結束標志),只把它們當做連續的數據流來處理。

下面的代碼演示了粘包問題,客戶端連續三次向服務器端發送數據,服務器端卻一次性接收到所有數據。

服務器端代碼 server.cpp:

 

#include <stdio.h>
#include <windows.h>
#pragma comment (lib, "ws2_32.lib")  //加載 ws2_32.dll
 
#define BUF_SIZE 100
 
int main(){
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);
 
    //創建套接字
    SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);
 
    //綁定套接字
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每個字節都用0填充
    sockAddr.sin_family = PF_INET;  //使用IPv4地址
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    sockAddr.sin_port = htons(1234);  //端口
    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
 
    //進入監聽狀態
    listen(servSock, 20);
 
    //接收客戶端請求
    SOCKADDR clntAddr;
    int nSize = sizeof(SOCKADDR);
    char buffer[BUF_SIZE] = {0};  //緩沖區
    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
 
    Sleep(10000);  //注意這里,讓程序暫停10秒
 
    //接收客戶端發來的數據,並原樣返回
    int recvLen = recv(clntSock, buffer, BUF_SIZE, 0);
    send(clntSock, buffer, recvLen, 0);
 
    //關閉套接字並終止DLL的使用
    closesocket(clntSock);
    closesocket(servSock);
    WSACleanup();
 
    return 0;
}

客戶端代碼 client.cpp:

#pragma comment(lib, "ws2_32.lib")  //加載 ws2_32.dll
#define BUF_SIZE 100
int main(){
    //初始化DLL
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
 
    //向服務器發起請求
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每個字節都用0填充
    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    sockAddr.sin_port = htons(1234);
 
    //創建套接字
    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
 
    //獲取用戶輸入的字符串並發送給服務器
    char bufSend[BUF_SIZE] = {0};
    printf("Input a string: ");
    gets(bufSend);
    for(int i=0; i<3; i++){
        send(sock, bufSend, strlen(bufSend), 0);
    }
    //接收服務器傳回的數據
    char bufRecv[BUF_SIZE] = {0};
    recv(sock, bufRecv, BUF_SIZE, 0);
    //輸出接收到的數據
    printf("Message form server: %s\n", bufRecv);
 
    closesocket(sock);  //關閉套接字
    WSACleanup();  //終止使用 DLL
 
    system("pause");
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM