Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 網路程式設計:健壯的讀寫函數實作(處理 EINTR、EPIPE 等錯誤)

編輯:Linux 教學

問題背景

網路程式中直接呼叫 read()/write() 存在風險:

  • 短計數: 返回值小於請求長度(緩衝區滿/空、訊號中斷)。
  • 訊號中斷 (EINTR): 慢系統呼叫被訊號處理程式打斷,需重試。
  • 連線斷裂 (EPIPE/ECONNRESET): 對端關閉,寫入觸發 SIGPIPE 或返回錯誤。

健壯的封裝函數應自動處理重試與錯誤轉換。

健壯寫入函數 write_n()

ssize_t write_n(int fd, const void *buf, size_t n) {
    size_t left = n;
    const char *ptr = (const char *)buf;
    ssize_t written;

    while (left > 0) {
        written = write(fd, ptr, left);
        if (written <= 0) {
            if (errno == EINTR) { written = 0; continue; } // 被中斷,重試
            if (errno == EPIPE) { return -1; }             // 連線斷裂,上層處理
            return -1;                                     // 其他錯誤
        }
        left -= written;
        ptr += written;
    }
    return n; // 成功寫入全部
}

健壯讀取函數 read_n()

ssize_t read_n(int fd, void *buf, size_t n) {
    size_t left = n;
    char *ptr = (char *)buf;
    ssize_t nread;

    while (left > 0) {
        nread = read(fd, ptr, left);
        if (nread < 0) {
            if (errno == EINTR) { nread = 0; continue; }
            return -1;
        } else if (nread == 0) { // EOF (對端關閉)
            break;
        }
        left -= nread;
        ptr += nread;
    }
    return n - left; // 返回實際讀取位元組數 (可能小於 n)
}

讀取固定長度資料:readline() 範例

針對行協議(以 \n 結尾),需逐字元讀取或使用緩衝:

ssize_t readline(int fd, void *buf, size_t maxlen) {
    char *ptr = (char *)buf;
    ssize_t n, rc;
    char c;

    for (n = 1; n < maxlen; n++) {
        rc = read(fd, &c, 1);
        if (rc == 1) {
            *ptr++ = c;
            if (c == '\n') break;
        } else if (rc == 0) { // EOF
            if (n == 1) return 0;
            break;
        } else {
            if (errno == EINTR) continue;
            return -1;
        }
    }
    *ptr = '\0';
    return n;
}

結構體傳輸範例

// 發送端
struct MyData { int id; char msg[100]; };
struct MyData data = { .id = 1, .msg = "Hello" };
if (write_n(sockfd, &data, sizeof(data)) != sizeof(data))
    handle_error("write_n");

// 接收端
struct MyData recv_data;
if (read_n(sockfd, &recv_data, sizeof(recv_data)) != sizeof(recv_data))
    handle_error("read_n");
// 注意:跨架構傳輸需處理位元組序 (htonl/ntohl) 與結構對齊

最佳實踐建議

  1. 統一使用封裝函數,避免直接呼叫 read/write。
  2. 發送結構體前,將整數欄位轉為網路序;接收後轉回主機序。
  3. 應用層協議設計採用「長度前綴」或「分隔符」幀界,避免黏包/拆包問題。
  4. 設定 Socket 選項 SO_SNDTIMEO/SO_RCVTIMEO 防止無限阻塞。
  5. 忽略 SIGPIPE (signal(SIGPIPE, SIG_IGN)),改由 write_n 返回 EPIPE 處理。
Copyright © Windows教程網 All Rights Reserved