socketpair 是 UNIX 系統編程中一個強大且常被忽視的系統呼叫,它能建立一對已連線的無名套接字,支援全雙工通訊。本文將從原理、單進程驗證、父子進程通訊、與 pipe 差異比較,到核心實作機制進行深度解析,並附上完整可編譯的程式碼範例。
AF_UNIX (或 AF_LOCAL) 域使用。s[0]、s[1] 均可讀可寫,互為對端。s[0] 可從 s[1] 讀取,反之亦然;同一描述符寫入後不可自我讀取。read 會阻塞,即使同一端已寫入資料也無法解除阻塞。#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <error.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdlib.h>
#define BUF_SIZE 30
int main() {
int s[2];
int w, r;
char *string = "This is a test string";
char *buf = (char*)calloc(1, BUF_SIZE);
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) {
printf("create unnamed socket pair failed: %s\n", strerror(errno));
exit(-1);
}
// 寫入 s[0],從 s[1] 讀取
if ((w = write(s[0], string, strlen(string))) == -1) {
printf("Write socket error: %s\n", strerror(errno));
exit(-1);
}
if ((r = read(s[1], buf, BUF_SIZE)) == -1) {
printf("Read from socket error: %s\n", strerror(errno));
exit(-1);
}
printf("Read string in same process: %s\n", buf);
// 驗證同一端不可讀取自己寫入的資料
if ((r = read(s[0], buf, BUF_SIZE)) == -1) {
printf("Read from socket s0 error: %s\n", strerror(errno));
exit(-1);
}
printf("Read from s0: %s\n", buf); // 應阻塞或讀不到剛寫入的資料
printf("Test successed\n");
return 0;
}
透過 fork() 建立子進程,父進程關閉 s[1],子進程關閉 s[0],即可實現雙向通訊:
// 關鍵片段
pid_t pid;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) { /* 錯誤處理 */ }
if ((pid = fork()) > 0) { // 父進程
close(s[1]);
write(s[0], string, strlen(string));
} else if (pid == 0) { // 子進程
close(s[0]);
read(s[1], buf, BUF_SIZE);
printf("Child read: %s\n", buf);
}
// 父子皆可互為讀寫端,實現全雙工
| 特性 | pipe | socketpair |
|---|---|---|
| 通訊模式 | 半雙工(單向) | 全雙工(雙向) |
| 描述符角色 | 固定讀端/寫端 | 對等,皆可讀寫 |
| 適用場景 | 單向資料流(如 Shell 管線) | 雙向互動(如父子進程協作、狀態同步) |
若父子進程皆不關閉任一描述符,兩者共享同一檔案表項,會對同一資料流產生競爭讀取:
read 會持續阻塞等待新資料。此行為可用「寫端引用計數」與「檔案表項共享」機制完整解釋。
socketpair 在核心中建立兩個 struct socket 物件,屬於虛擬網路檔案系統(非實體檔案系統)。peer 指標互指,形成邏輯迴路,核心內部以環狀緩衝區實現資料傳遞。read/write 實際轉呼叫 socket 家族的 sock_read/sock_write。fork 時子進程繼承父進程的檔案描述符表,指向相同的 struct file 與 socket 物件,因此共享偏移量、緩衝區與引用計數。socketpair 是進程間通訊(IPC)的利器,特別適合需要雙向、低延遲、無需命名的通訊場景。理解其引用計數、EOF 語意與核心實作,有助於避免常見的阻塞、資料搶讀等陷阱。建議在父子進程協作、事件迴路內部通知等場景優先考慮使用。