Linux C语言 socketpair 详解与实战示例
socketpair是Linux系统下用于创建一对已连接且无名的Unix域套接字的系统调用,特别适合父子进程间或同一进程内的双向通信。通过AF_UNIX协议,它提供了基于流的可靠数据传输能力,无需绑定IP地址和端口。
结果与目标
使用 socketpair() 能够高效地在两个文件描述符之间建立双向通信通道。这种机制常用于进程间通信(IPC),特别是在 fork 生成的父子进程中,或者在需要避免网络栈开销的内部模块交互中。相比传统的管道(pipe)或命名Socket,socketpair 支持全双工通信,即两个端点均可同时进行独立的读和写操作。
准备条件
- 操作系统环境:Linux 系统(该函数并非 POSIX 标准的一部分,但在 Linux/BSD/macOS 中广泛支持)。
- 头文件依赖:
<sys/socket.h>:声明socketpair及其参数类型。<sys/types.h>:基本数据类型定义。
- 理解基本概念:
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 后分别关闭不需要的描述符。
关键逻辑:
- Fork之前:先创建 socketpair。
- 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本地通信的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













