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

C++ Socket 程式設計:連接/收發超時設定與多網卡綁定完整指南

     編輯:C++ 入門

收發超時設定 (SO_RCVTIMEO / SO_SNDTIMEO)

適用於阻塞 Socket,防止 recv/send 無限等待。


int timeoutMs = 1000; // 1 秒
// 接收超時
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, 
           reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs));
// 發送超時
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, 
           reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs));

行為:超時後 recv/send 返回 SOCKET_ERROR,WSAGetLastError() == WSAETIMEDOUT (Windows) 或 errno == EAGAIN/EWOULDBLOCK (Linux)。

連接超時:connect 非阻塞 + select 實現

connect 無標準超時選項,需手動實現:


bool connectWithTimeout(SOCKET sock, const sockaddr* addr, int addrLen, int timeoutMs) {
    // 1. 設置非阻塞模式
    u_long nonBlocking = 1;
    ioctlsocket(sock, FIONBIO, &nonBlocking); // Windows
    // fcntl(sock, F_SETFL, O_NONBLOCK); // Linux
    
    // 2. 發起連接
    int ret = connect(sock, addr, addrLen);
    if (ret == 0) { // 立即成功 (本地回環等)
        nonBlocking = 0;
        ioctlsocket(sock, FIONBIO, &nonBlocking);
        return true;
    }
    
    // 3. 非阻塞下預期錯誤
    int err = WSAGetLastError(); // errno
    if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS && err != EINPROGRESS) {
        nonBlocking = 0;
        ioctlsocket(sock, FIONBIO, &nonBlocking);
        return false; // 真實錯誤
    }
    
    // 4. select 監視可寫狀態 (連接完成)
    fd_set writeSet;
    FD_ZERO(&writeSet);
    FD_SET(sock, &writeSet);
    
    timeval tv;
    tv.tv_sec = timeoutMs / 1000;
    tv.tv_usec = (timeoutMs % 1000) * 1000;
    
    ret = select(0, nullptr, &writeSet, nullptr, &tv);
    nonBlocking = 0;
    ioctlsocket(sock, FIONBIO, &nonBlocking); // 恢復阻塞
    
    if (ret <= 0) return false; // 超時或錯誤
    
    // 5. 獲取連接真實結果 (SO_ERROR)
    int sockErr = 0;
    int errLen = sizeof(sockErr);
    getsockopt(sock, SOL_SOCKET, SO_ERROR, 
               reinterpret_cast<char*>(&sockErr), &errLen);
    return sockErr == 0;
}

多網卡綁定發送:指定源 IP

多網卡服務器主動連接時,需綁定特定網卡 IP 作為源地址:


bool bindToInterface(SOCKET sock, const char* localIp, uint16_t localPort = 0) {
    sockaddr_in localAddr{};
    localAddr.sin_family = AF_INET;
    localAddr.sin_addr.s_addr = inet_addr(localIp); // 指定網卡 IP
    localAddr.sin_port = htons(localPort); // 0 讓系統自動分配端口
    
    int ret = bind(sock, reinterpret_cast<sockaddr*>(&localAddr), sizeof(localAddr));
    if (ret == SOCKET_ERROR) {
        // WSAEADDRNOTAVAIL: IP 不屬於本機任何網卡
        // WSAEADDRINUSE: 端口已被佔用
        return false;
    }
    return true;
}

// 使用示例:雙網卡服務器按業務分流
SOCKET sockBiz = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bindToInterface(sockBiz, "192.168.1.100"); // 業務網卡
connect(sockBiz, &bizServerAddr, sizeof(bizServerAddr));

SOCKET sockMgmt = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bindToInterface(sockMgmt, "10.0.0.50"); // 管理網卡
connect(sockMgmt, &mgmtServerAddr, sizeof(mgmtServerAddr));

跨平台封裝建議


class Socket {
    SOCKET fd_;
public:
    Socket() : fd_(socket(AF_INET, SOCK_STREAM, 0)) {}
    ~Socket() { close(); }
    
    bool setRecvTimeout(int ms) {
        return setsockopt(fd_, SOL_SOCKET, SO_RCVTIMEO, 
                          (const char*)&ms, sizeof(ms)) == 0;
    }
    bool setSendTimeout(int ms) {
        return setsockopt(fd_, SOL_SOCKET, SO_SNDTIMEO, 
                          (const char*)&ms, sizeof(ms)) == 0;
    }
    bool connectWithTimeout(const std::string& ip, uint16_t port, int timeoutMs) {
        sockaddr_in addr{}; addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(ip.c_str());
        addr.sin_port = htons(port);
        return connectWithTimeoutImpl(fd_, addr, timeoutMs);
    }
    bool bindInterface(const std::string& localIp) {
        return bindToInterface(fd_, localIp.c_str());
    }
private:
    // ... 內部實現 ...
};

常見錯誤排查表

現象可能原因排查步驟
connect 超時不生效未設置非阻塞、select 參數錯誤檢查 ioctlsocket/fcntl 返回值、select 第一參數 (Windows 忽略、Linux 為 maxfd+1)
recv 超時後再次調用立即返回超時不清除錯誤狀態Windows 需無額外處理;Linux 需檢查 errno 重置
bind 失敗 WSAEADDRNOTAVAILIP 不屬於本機ipconfig / ifconfig 確認 IP 存在、子網掩碼正確
多網卡仍走默認路由bind 後未 connect、或連接本地回環確保 bind 在 connect 之前、目標 IP 非本地
高併發下文件描述符耗盡未及時 close、未設置 SO_REUSEADDRRAII 管理 Socket、setsockopt SO_REUSEADDR、調大 ulimit -n

現代化替代方案

  • Boost.Asio:跨平台、Proactor 模式、支援協程 (C++20)、Timer/SSL/HTTP 內建。
  • libuv:Node.js 底層庫、高性能事件循環、C API。
  • ASIO 獨立版:Header-only、無 Boost 依賴、C++11+。
  • C++23 Networking TS:標準化進行中,基於 Asio 設計。

總結

  1. 收發超時:setsockopt + SO_RCVTIMEO/SO_SNDTIMEO,簡單有效。
  2. 連接超時:非阻塞 connect + select 監視可寫 + getsockopt(SO_ERROR) 驗證,跨平台通用模式。
  3. 多網卡綁定:bind 指定本地 IP,必須在 connect 之前調用。
  4. 生產代碼建議封裝 RAII 類,或直接採用 Boost.Asio 等成熟框架,避免重複造輪子。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved