Linux C语言 socketpair 详解与实战示例

季宁
季宁
发布时间:2026-10-16| 5人浏览过
原创

socketpair是Linux系统下用于创建一对已连接且无名的Unix域套接字的系统调用,特别适合父子进程间或同一进程内的双向通信。通过AF_UNIX协议,它提供了基于流的可靠数据传输能力,无需绑定IP地址和端口。

结果与目标

使用 socketpair() 能够高效地在两个文件描述符之间建立双向通信通道。这种机制常用于进程间通信(IPC),特别是在 fork 生成的父子进程中,或者在需要避免网络栈开销的内部模块交互中。相比传统的管道(pipe)或命名Socket,socketpair 支持全双工通信,即两个端点均可同时进行独立的读和写操作。

准备条件

  1. 操作系统环境:Linux 系统(该函数并非 POSIX 标准的一部分,但在 Linux/BSD/macOS 中广泛支持)。
  2. 头文件依赖:
    • <sys/socket.h>:声明 socketpair 及其参数类型。
    • <sys/types.h>:基本数据类型定义。
  3. 理解基本概念:
    • fd[0] 与 fd[1] 是镜像关系:往 fd[0] 写入的数据只能从 fd[1] 读取,反之亦然。
    • type 通常选择 SOCK_STREAM(面向连接的字节流)以获取类似TCP的稳定传输;也可选 SOCK_DGRAM(数据报)。
    • domain 通常为 AF_UNIX(或 AF_LOCAL),表示仅在主机本地通信。

真实操作顺序

第一步:基础API原型确认

在使用前,需明确函数的签名:

#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);

返回值说明: - 成功返回 0。 - 失败返回 -1,错误信息存储于 errno 中。 - sv 数组被填充为两个有效的文件描述符 sv[0] 和 sv[1]。

第二步:同进程内的简单测试验证

若需在同一个进程中验证连通性,可利用“写A读B”的逻辑。注意,如果尝试在同一端(如从sv[0]读刚才写入sv[0]的数据)会阻塞,因为数据必须跨越到另一端才能被读取。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>

const char* str = "SOCKET PAIR TEST.";

int main() {
    char buf[128] = {0};
    int socket_pair[2]; 

    // 1. 创建 socket 对
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair) == -1 ) { 
        perror("socketpair failed");
        return EXIT_FAILURE; 
    } 

    // 2. 向 socket_pair[0] 发送数据
    write(socket_pair[0], str, strlen(str));
    
    // 3. 从 socket_pair[1] 接收数据(对应写入的另一端)
    read(socket_pair[1], buf, sizeof(buf));
    printf("Read result: %s\n", buf);

    close(socket_pair[0]);
    close(socket_pair[1]);
    return EXIT_SUCCESS;    
}

第三步:构建经典的父子进程通信模型

这是 socketpair 最典型的应用场景。父进程 fork 子进程后,双方各自持有原始的 socket pair。为了规范通信,通常约定一方负责写,另一方负责读,并在 fork 后分别关闭不需要的描述符。

关键逻辑:

  1. Fork之前:先创建 socketpair。
  2. Fork之后:
  • 子进程(PID == 0):关闭写端 [0],只保留读端 [1]。
  • 父进程(PID > 0):关闭读端 [1],只保留写端 [0]。
#include <stdio.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdlib.h> 

const char* str = "CHILD PROCESS DATA";

int main() {
    char buf[128] = {0};
    int socket_pair[2]; 
    pid_t pid; 

    // 1. 创建配对套接字
    if(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair) == -1 ) { 
        perror("socketpair failed");
        return EXIT_FAILURE;
    } 

    // 2. 创建子进程
    pid = fork();
    if(pid < 0) {
        perror("fork failed");
        return EXIT_FAILURE;
    } 

    if(pid > 0) { // 父进程
        // 关闭读取端,专注发送
        close(socket_pair[1]); 
        
        write(socket_pair[0], str, strlen(str));
        printf("Parent [%d] sent data.\n", getpid());
        // 发送结束后关闭,通知子进程EOF
        close(socket_pair[0]); 
    } else if(pid == 0) { // 子进程
        // 关闭发送端,专注接收
        close(socket_pair[0]); 
        
        ssize_t n = read(socket_pair[1], buf, sizeof(buf)-1);
        if(n > 0) {
            buf[n] = '\0';
            printf("Child [%d] received: %s\n", getpid(), buf);
        }
        close(socket_pair[1]);
    }
    return EXIT_SUCCESS;    
}

第四步:进阶使用 sendmsg/recvmsg 传递结构化数据

对于更复杂的数据结构或需要附带控制信息的场景,推荐使用 sendmsg 和 recvmsg。它们通过 msghdr 结构体管理消息。

/* 使用 sendmsg/recvmsg 与 socketpair 的完整示例 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

int main() {
    int sock[2];
    struct msghdr msg;
    struct iovec iov[1];
    char send_buf[] = "Hello via SendMsg";
    char recv_buf[64];
    
    // 1. 初始化 Socket Pair
    if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sock) == -1) {
        perror("socketpair");
        return 1;
    }

    /* 准备发送数据结构 */
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    iov[0].iov_base = send_buf;
    iov[0].iov_len = strlen(send_buf);
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    // 2. 发送方(sock[1])发送数据
    if (sendmsg(sock[1], &msg, 0) == -1) {
        perror("sendmsg failed");
        return -1;
    }
    close(sock[1]); // 发送完毕后关闭写端

    /* 准备接收数据结构 */
    memset(&msg, 0, sizeof(msg));
    iov[0].iov_base = recv_buf;
    iov[0].iov_len = sizeof(recv_buf);
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    // 3. 接收方(sock[0])接收数据
    ssize_t ret = recvmsg(sock[0], &msg, 0);
    if (ret > 0) {
        recv_buf[ret] = '\0'; // 手动添加结束符
        printf("Received: %s\n", recv_buf);
    }
    close(sock[0]);

    return 0;
}

完成判断与排错指南

如何判断成功? - socketpair 返回 0。 - 子进程收到父进程发送的字符串,打印出预期内容,且未出现段错误或阻塞挂起。

常见问题排查: EBADF:使用的文件描述符无效或已关闭。
EAGAIN:非阻塞模式下,缓冲区满或为空时发生。若使用的是默认阻塞模式,通常不会出现此问题,除非使用了复杂的标志位。
EPROTONOSUPPORT:在某些严格遵循POSIX的系统上,如果指定的协议不支持 socketpair,可能报错。确保设置 protocol 为 0,让系统自动选择(Unix域通常为 0)。

简要总结

Socketpair 提供了一种轻量级、高性能的本地双向通信方案,特别适用于 Linux C/C++ 开发中的多进程架构。相比于命名管道(Named Pipe)或 Unix Socket File,它无需创建临时文件系统节点,且在 fork 后的继承关系处理非常直观。掌握其与 sendmsg/recvmsg 的结合使用,能进一步提升处理二进制消息和控制元数据的灵活性。

以上就是Linux C语言 socketpair详解与实战示例的详细内容,更多关于C语言 socketpair本地通信的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。