首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ Socket TCP 開發全流程總結:從 API 到完整客戶端/服務端代碼

     編輯:關於C++

前言

Windows Socket API (Winsock) 是 C++ 網絡編程基石。本文系統總結 TCP 流式套接字開發的標準流程、核心 API 調用序列、錯誤處理要點,並提供可編譯運行的完整服務端/客戶端參考代碼,適合快速上手與面試複習。

一、三類套接字類型

類型宏協議特點
流式套接字SOCK_STREAMTCP面向連接、可靠、有序、字節流、無邊界
數據報套接字SOCK_DGRAMUDP無連接、不可靠、無序、有邊界、支援廣播/多播
原始套接字SOCK_RAWIP/ICMP/自定義直接訪問網絡層,需管理員權限

本文專注 SOCK_STREAM (TCP)。

二、標準開發流程

1. 服務端 7 步驟

  1. 加載庫 & 創建套接字:WSAStartup() → socket(AF_INET, SOCK_STREAM, 0)
  2. 綁定地址端口:bind(sock, (sockaddr*)&addr, sizeof(addr))
  3. 監聽:listen(sock, backlog)(backlog 通常 5~1024)
  4. 接受連接:accept(sock, &clientAddr, &len) → 返回**新套接字**用於通信
  5. 收發數據:send(newSock, ...) / recv(newSock, ...)
  6. 循環等待下一連接:回到第 4 步
  7. 清理:closesocket() → WSACleanup()

2. 客戶端 4 步驟

  1. 加載庫 & 創建套接字:同服務端
  2. 連接服務端:connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr))
  3. 收發數據:send/recv
  4. 清理:同服務端

三、關鍵 API 詳解

1. 庫初始化/清理


WSADATA wsaData;
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) { /* 錯誤處理 */ }
// ...
WSACleanup();

2. 地址結構填充


sockaddr_in addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);           // 主機序轉網絡序
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 服務端綁定所有網卡
// 客戶端連接時:addr.sin_addr.s_addr = inet_addr("192.168.1.100");

3. 數據收發


// 發送:返回實際發送字節數,可能 < 請求長度(需循環發送)
int send(SOCKET s, const char* buf, int len, int flags);

// 接收:返回實際接收字節數,0 表示對端關閉,SOCKET_ERROR 表示錯誤
int recv(SOCKET s, char* buf, int len, int flags);
關鍵:TCP 是字節流,**無消息邊界**,應用層必須自行實現分幀(長度前綴、分隔符、固定長度等)。

四、完整參考代碼

1. 服務端 (Server.cpp)


#include 
#include 
#pragma comment(lib, "ws2_32.lib")

int main() {
    // 1. 初始化
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
        printf("WSAStartup failed\n"); return -1;
    }

    // 2. 創建監聽套接字
    SOCKET listenSock = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSock == INVALID_SOCKET) { /* 錯誤 */ }

    // 3. 綁定
    sockaddr_in serverAddr;
    ZeroMemory(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(6000);
    serverAddr.sin_addr.s_addr = inet_addr("192.168.245.221"); // 或 INADDR_ANY
    if (bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { /* 錯誤 */ }

    // 4. 監聽
    if (listen(listenSock, 5) == SOCKET_ERROR) { /* 錯誤 */ }
    printf("Server listening on port 6000...\n");

    // 5. 循環接受
    while (true) {
        sockaddr_in clientAddr;
        int addrLen = sizeof(clientAddr);
        SOCKET clientSock = accept(listenSock, (sockaddr*)&clientAddr, &addrLen);
        if (clientSock == INVALID_SOCKET) continue;

        printf("Client connected: %s\n", inet_ntoa(clientAddr.sin_addr));

        // 發送歡迎消息
        char sendBuf[100];
        sprintf(sendBuf, "Welcome %s to server", inet_ntoa(clientAddr.sin_addr));
        send(clientSock, sendBuf, strlen(sendBuf)+1, 0);

        // 接收回覆
        char recvBuf[100];
        int ret = recv(clientSock, recvBuf, sizeof(recvBuf)-1, 0);
        if (ret > 0) {
            recvBuf[ret] = '\0';
            printf("Received: %s\n", recvBuf);
        }

        closesocket(clientSock); // 關閉客戶端套接字
    }

    closesocket(listenSock);
    WSACleanup();
    return 0;
}

2. 客戶端 (Client.cpp)


#include 
#include 
#pragma comment(lib, "ws2_32.lib")

int main() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) return -1;

    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    
    sockaddr_in serverAddr;
    ZeroMemory(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(6000);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服務端 IP

    if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        printf("Connect failed\n");
        closesocket(sock);
        WSACleanup();
        return -1;
    }

    // 接收歡迎消息
    char recvBuf[100];
    int ret = recv(sock, recvBuf, sizeof(recvBuf)-1, 0);
    if (ret > 0) {
        recvBuf[ret] = '\0';
        printf("Server: %s\n", recvBuf);
    }

    // 發送回覆
    const char* msg = "Hello, this is client";
    send(sock, msg, strlen(msg)+1, 0);

    closesocket(sock);
    WSACleanup();
    return 0;
}

五、常見問題與最佳實踐

  1. 粘包/半包處理:應用層協議必須定義消息邊界(如:4 字節長度 + 數據體),接收端維護緩衝區循環解析。
  2. 非阻塞/異步 IO:高併發場景使用 select/WSAAsyncSelect/IOCP (完成端口),避免線程阻塞。
  3. 錯誤碼獲取:WSAGetLastError() 獲取具體錯誤(如 WSAECONNRESET 連接重置)。
  4. 端口復用:setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, ...) 避免 TIME_WAIT 導致重啟失敗。
  5. 字節序轉換:網絡傳輸統一用**大端序**,htons/htonl (主機→網絡),ntohs/ntohl (網絡→主機)。
  6. 資源清理:確保所有路徑(正常/異常/提前返回)都調用 closesocket 和 WSACleanup,推薦 RAII 封裝。
  7. 防火牆/殺毒軟件:測試時注意放行端口,否則 connect 超時或 accept 無反應。

六、跨平台提示

  • Linux/Unix:<sys/socket.h>、<netinet/in.h>、<arpa/inet.h>、<unistd.h> (close)。
  • 頭文件、函數名、錯誤碼常量基本一致,主要差異:SOCKET 類型 (Windows 為 SOCKET_PTR,Linux 為 int)、INVALID_SOCKET (-1)、closesocket → close、WSAGetLastError → errno。
  • 推薦使用跨平台庫:ASIO (Boost.Asio/standalone)、libuv、POCO Net、Qt Network。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved