在區域網路環境,頻繁建立/銷毀 Socket 開銷極大。採用長連結(持久連線)配合長度前綴協議,可實現高效雙向資料流傳輸。本文提供基於 select I/O 多路複用的完整 Client/Server 參考實作。
+--------+------------------+
| 8 bytes| Payload |
| Length | (Length bytes) |
+--------+------------------+
// 關鍵資料結構
struct ClientInfo { std::string host; int port; };
std::map<int, ClientInfo> clients; // fd -> 客戶端資訊
// select 事件迴圈
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds);
while (true) {
int nfds = select(FD_SETSIZE, &read_fds, NULL, NULL, NULL);
if (FD_ISSET(server_fd, &read_fds)) {
// 接受新連線
int client_fd = accept(...);
FD_SET(client_fd, &read_fds);
clients[client_fd] = {ip, port};
} else {
// 處理客戶端資料
for (auto& [fd, info] : clients) {
if (FD_ISSET(fd, &read_fds)) {
handle_client(fd, info);
break; // 簡化:單執行緒一次處理一個
}
}
}
}
// 發送:組裝長度前綴 + 資料,確保完整寫入
std::string send_buf = fmt::format("{:08d}", payload.size()) + payload;
size_t sent = 0;
while (sent < send_buf.size()) {
ssize_t n = write(fd, send_buf.data() + sent, send_buf.size() - sent);
if (n <= 0) handle_error();
sent += n;
}
// 接收:先讀 8 位元組長度,再按長度讀取
char len_buf[9];
if (recv(fd, len_buf, 8, 0) != 8) handle_disconnect();
len_buf[8] = '\0';
int payload_len = atoi(len_buf);
std::string payload;
while (payload.size() < payload_len) {
char buf[1024];
ssize_t n = recv(fd, buf, std::min(sizeof(buf), payload_len - payload.size()), 0);
if (n <= 0) handle_disconnect();
payload.append(buf, n);
}
connect 連線 Serverg++ server.cc -o server -std=c++11
g++ client.cc -o client -std=c++11
# 終端機 1
./server 7763
# 終端機 2
./client localhost 7763 "Hello Server"
epoll (Linux) / kqueue (BSD/macOS) / IOCP (Windows) 替代 select,支援更高併發文章包含完整可編譯的 server.cc (260 行) 與 client.cc (169 行),涵蓋命令列解析、錯誤處理、緩衝區管理、優雅關閉等工程細節,適合網路程式設計入門參考。