經過 socket()、bind()/connect()、listen()/accept(),我們終於擁有了可通訊的 connected socket。send() 與 recv() 是串流式套接字 (TCP) 的資料收發主力,也可用於資料報套接字 (UDP)。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:connected socket 描述符 (來自 socket() 或 accept())buf:資料緩衝區指標len:欲發送位元組數flags:通常為 0。常用旗標:
MSG_NOSIGNAL:避免連線中斷時產生 SIGPIPE 訊號 (Linux)MSG_DONTWAIT:單次非阻塞發送MSG_MORE:告知核心還有更多資料待發送,優化封包合併 (Linux)send() 回傳值可能小於 len! 核心發送緩衝區可能已滿,或網路裝置無法一次處理大量資料。
// ❌ 錯誤:假設一次發送完畢
send(sockfd, msg, len, 0);
// ✅ 正確:迴圈處理部分發送
ssize_t total_sent = 0;
while (total_sent < len) {
ssize_t sent = send(sockfd, static_cast(buf) + total_sent,
len - total_sent, 0);
if (sent == -1) {
if (errno == EINTR) continue; // 被訊號中斷,重試
perror("send");
break;
}
total_sent += sent;
}
errno (EAGAIN/EWOULDBLOCK 非阻塞無緩衝、EPIPE 連線斷裂、ENOTCONN 未連線)ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:connected socketbuf:接收緩衝區len:緩衝區大小 (上限)flags:常用:
MSG_PEEK:窺視資料但不從緩衝區移除MSG_WAITALL:等待滿 len 才返回 (除非 EOF/錯誤)MSG_DONTWAIT:非阻塞TCP 是位元組串流,無訊息邊界。recv() 回傳「目前可用資料量」,可能:
len (正常)len (緩衝區剛好滿)
// 指定長度接收 (如已知封包大小)
ssize_t recv_exact(int sockfd, void *buf, size_t len) {
size_t total = 0;
while (total < len) {
ssize_t n = recv(sockfd, static_cast(buf) + total, len - total, 0);
if (n == 0) return -1; // EOF
if (n == -1) {
if (errno == EINTR) continue;
return -1;
}
total += n;
}
return total;
}
// 行導向接收 (直到 \n)
std::string recv_line(int sockfd) {
std::string line;
char ch;
while (recv_exact(sockfd, &ch, 1) == 1) {
if (ch == '\n') break;
if (ch != '\r') line += ch; // 處理 CRLF
}
return line;
}
while (1) {
int client_fd = accept(sockfd, ...);
char buf[1024];
ssize_t n;
while ((n = recv(client_fd, buf, sizeof(buf), 0)) > 0) {
send(client_fd, buf, n, 0); // 回傳收到的資料
}
close(client_fd);
}
send(sockfd, "Hello, Server!", 14, 0);
char buf[1024];
ssize_t n = recv(sockfd, buf, sizeof(buf), 0);
if (n > 0) std::cout.write(buf, n);
O_NONBLOCK 旗標select()/poll()/epoll()/kqueue() 監控可讀/可寫事件較舊技術,現代首選 epoll/kqueue/IOCP。
// Linux sendfile() / splice() - 檔案直接傳送到 socket,避免使用者空間拷貝
sendfile(out_fd, in_fd, &offset, count);
| 現象 | 原因 | 解決 |
|---|---|---|
| send 回傳 -1, errno=EPIPE | 對端已關閉 | 忽略 SIGPIPE (signal(SIGPIPE, SIG_IGN) 或 MSG_NOSIGNAL) |
| recv 回傳 0 | 對端正常關閉 (FIN) | 關閉 socket,結束通訊迴圈 |
| 資料截斷/亂序 | 未處理部分發送/接收 | 實作迴圈確保完整收發 |
| 大封包效能差 | Nagle 演算法延遲小封包 | TCP_NODELAY 關閉 Nagle (即時遊戲等) |
send()/recv() 是網路程式的「讀/寫」原語。核心心法三點:**處理部分發送/接收、區分 EOF 與錯誤、理解 TCP 無訊息邊界**。掌握這些,你就能實作從簡單 Echo 到高效能 HTTP 伺服器的各種網路應用。Beej's Guide 核心系統呼叫系列到此告一段落,後續將探討進階主題 (非阻塞、多路複用、TLS 等)。