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

Beej's Guide to Network Programming 系列連載 14:send() 與 recv() — 資料收發核心

     編輯:關於C語言

前言:連線建立後的核心操作

經過 socket()、bind()/connect()、listen()/accept(),我們終於擁有了可通訊的 connected socket。send() 與 recv() 是串流式套接字 (TCP) 的資料收發主力,也可用於資料報套接字 (UDP)。

send() — 發送資料

#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;
}

回傳值

  • 成功:實際發送位元組數 (>0)
  • 對端關閉:回傳 0 (TCP)
  • 錯誤:-1,設定 errno (EAGAIN/EWOULDBLOCK 非阻塞無緩衝、EPIPE 連線斷裂、ENOTCONN 未連線)

recv() — 接收資料

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

參數說明

  • sockfd:connected socket
  • buf:接收緩衝區
  • len:緩衝區大小 (上限)
  • flags:常用:
    • MSG_PEEK:窺視資料但不從緩衝區移除
    • MSG_WAITALL:等待滿 len 才返回 (除非 EOF/錯誤)
    • MSG_DONTWAIT:非阻塞

關鍵特性:資料流無邊界

TCP 是位元組串流,無訊息邊界。recv() 回傳「目前可用資料量」,可能:

  • 小於 len (正常)
  • 等於 len (緩衝區剛好滿)
  • 為 0 (對端正常關閉,EOF)

正確接收模式


// 指定長度接收 (如已知封包大小)
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;
}

完整 Echo 伺服器/客戶端範例

伺服器


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);

進階主題

非阻塞 I/O 與 I/O 多路複用

  • 設定 O_NONBLOCK 旗標
  • 配合 select()/poll()/epoll()/kqueue() 監控可讀/可寫事件
  • 單執行緒處理上萬連線

訊號驅動 I/O (SIGIO)

較舊技術,現代首選 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 等)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved