Windows Sockets (Winsock) 原始API充滿錯誤碼檢查、資源洩漏隱患、同步阻塞模型。本文展示如何運用C++異常機制、RAII、移動語義,將其封裝為現代、安全、易用的Socket庫雛形。
// 典型Winsock原始代碼:錯誤處理淹沒業務邏輯
if ((ErrorCode = WSAStartup(MAKEWORD(2,1), &wsaData)) != 0) { /* ... */ }
sockMain = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, WSA_FLAG_OVERLAPPED);
if (sockMain == INVALID_SOCKET) { /* ... */ }
if (setsockopt(sockMain, IPPROTO_IP, IP_HDRINCL, ...) == SOCKET_ERROR) { /* ... */ }
問題:錯誤碼檢查與業務代碼耦合、資源獲取與釋放分離、無法構建異常安全保證。
class Socket {
SOCKET handle_ = INVALID_SOCKET;
bool non_blocking_ = false;
public:
// 建構:支援自定義協議族/類型/協議
explicit Socket(int af = AF_INET, int type = SOCK_STREAM, int protocol = 0) {
handle_ = WSASocket(af, type, protocol, nullptr, 0, WSA_FLAG_OVERLAPPED);
if (handle_ == INVALID_SOCKET)
throw SocketException("WSASocket failed", WSAGetLastError());
}
// 移動語義:所有權轉移,禁止複製
Socket(Socket&& other) noexcept : handle_(other.handle_) { other.handle_ = INVALID_SOCKET; }
Socket& operator=(Socket&& other) noexcept {
if (this != &other) { Close(); handle_ = other.handle_; other.handle_ = INVALID_SOCKET; }
return *this;
}
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
~Socket() { Close(); }
// 選項設置:異常安全鏈式調用
Socket& SetOption(int level, int optname, const void* optval, int optlen) {
if (setsockopt(handle_, level, optname, static_cast<const char*>(optval), optlen) == SOCKET_ERROR)
throw SocketException("setsockopt failed", WSAGetLastError());
return *this;
}
Socket& EnableNonBlocking() {
u_long mode = 1;
if (ioctlsocket(handle_, FIONBIO, &mode) == SOCKET_ERROR)
throw SocketException("ioctlsocket FIONBIO failed", WSAGetLastError());
non_blocking_ = true;
return *this;
}
// 核心操作
void Bind(const sockaddr* addr, int addrlen) { CheckErr(bind(handle_, addr, addrlen), "bind"); }
void Listen(int backlog = SOMAXCONN) { CheckErr(listen(handle_, backlog), "listen"); }
Socket Accept(sockaddr* addr = nullptr, int* addrlen = nullptr) {
SOCKET client = accept(handle_, addr, addrlen);
if (client == INVALID_SOCKET) throw SocketException("accept failed", WSAGetLastError());
return Socket(client); // 移動構造
}
// Send/Recv/Connect/Close 類似...
SOCKET NativeHandle() const { return handle_; }
explicit operator bool() const { return handle_ != INVALID_SOCKET; }
private:
explicit Socket(SOCKET h) : handle_(h) {} // 供 Accept 內部使用
void Close() { if (handle_ != INVALID_SOCKET) { closesocket(handle_); handle_ = INVALID_SOCKET; } }
static void CheckErr(int ret, const char* op) { if (ret == SOCKET_ERROR) throw SocketException(op, WSAGetLastError()); }
};
class SocketException : public std::runtime_error {
int error_code_;
public:
SocketException(const std::string& msg, int code)
: std::runtime_error(msg + ": " + std::to_string(code)), error_code_(code) {}
int ErrorCode() const noexcept { return error_code_; }
// 分類異常支援精確捕獲
class WouldBlock : public SocketException { /* EWOULDBLOCK/EAGAIN */ };
class ConnectionReset : public SocketException { /* ECONNRESET */ };
class TimedOut : public SocketException { /* ETIMEDOUT */ };
};
class WsaInitializer {
bool initialized_ = false;
public:
WsaInitializer(WORD version = MAKEWORD(2,2)) {
WSADATA wsaData;
int err = WSAStartup(version, &wsaData);
if (err != 0) throw SocketException("WSAStartup failed", err);
initialized_ = true;
}
~WsaInitializer() { if (initialized_) WSACleanup(); }
WsaInitializer(const WsaInitializer&) = delete;
WsaInitializer& operator=(const WsaInitializer&) = delete;
};
void RunEchoServer(uint16_t port) {
WsaInitializer wsa; // 範圍守衛,自動清理
Socket server(AF_INET, SOCK_STREAM, IPPROTO_TCP);
server.SetOption(SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int))
.Bind(reinterpret_cast<sockaddr*>(&sockaddr_in{AF_INET, htons(port), INADDR_ANY}), sizeof(sockaddr_in))
.Listen();
std::cout << "Server listening on port " << port << std::endl;
while (true) {
try {
Socket client = server.Accept();
std::thread([client = std::move(client)]() mutable {
try {
char buf[4096];
while (true) {
int n = client.Recv(buf, sizeof(buf));
if (n <= 0) break;
client.Send(buf, n);
}
} catch (const SocketException::ConnectionReset&) {
// 客戶端異常斷開,正常結束
} catch (const SocketException& e) {
std::cerr << "Client error: " << e.what() << std::endl;
}
}).detach();
} catch (const SocketException& e) {
std::cerr << "Accept error: " << e.what() << std::endl;
}
}
}
| 階段 | 關鍵技術 | 目標 |
|---|---|---|
| 同步阻塞 | RAII、異常、移動語義 | 正確性、資源安全、易用性 |
| 同步非阻塞 | select/poll/epoll/IOCP、Reactor模式 | 高併發、單線程多連接 |
| 異步IO | Overlapped IO / io_uring、Proactor模式 | 零拷貝、核心協作、極致吞吐 |
| 應用層協議 | HTTP/2、gRPC、WebSocket、QUIC | 業務解耦、標準化互操作 |
std::expected (C++23) / Result<T,E>noexcept 標記不拋操作,[[nodiscard]] 強制處理返回值這個封裝庫雛形雖簡,但確立了現代C++網絡程式設計的骨架:正確性優先、資源自動化、錯誤顯式化、效能零開銷。