好的,我们继续按照您的列表顺序,介绍下一个函数socketpair 是一个 Linux 系统调用,用于创建一对相互连接的匿名套接字。这对套接字就像一个双向的管道(bidirectional pipe),允许两个进程(通常是具有亲缘关系的进程,如父子进程或线程)通过套接字语义进行双向(全双工)通信。
1. 函数介绍 socketpair 是一个 Linux 系统调用,用于创建一对相互连接的匿名套接字。这对套接字就像一个双向的管道(bidirectional pipe),允许两个进程(通常是具有亲缘关系的进程,如父子进程或线程)通过套接字语义进行双向(全双工)通信。
你可以把它想象成一对连在一起的对讲机:
与 pipe 创建的单向管道不同,socketpair 创建的是双向的。虽然 socketpair 创建的是套接字,但它们是匿名的,没有关联网络地址(如 IP 和端口),只能用于本地进程间通信。
2. 函数原型 1 2 3 4 #include <sys/socket.h> int socketpair (int domain, int type, int protocol, int sv[ ;2 ]) ;
3. 功能
创建套接字对: 请求内核创建两个未命名(匿名)的、相互连接的套接字。
返回文件描述符: 将这两个套接字的文件描述符通过 sv 数组返回给调用者。
sv[0] 是第一个套接字的文件描述符。
sv[1] 是第二个套接字的文件描述符。
建立连接: 这两个套接字在创建后就处于已连接状态,数据可以从 sv[0] 写入并从 sv[1] 读出,反之亦然。
4. 参数 int domain: 指定套接字的通信域。
int type: 指定套接字的类型。
SOCK_STREAM: 流式套接字。提供有序、可靠、双向的字节流服务。数据传输没有边界(像管道一样)。
SOCK_DGRAM: 数据报套接字。提供无连接、不可靠、有边界的数据报服务。每个 send/write 调用对应一个独立的消息单元。
(在 Linux 上,SOCK_SEQPACKET 也可能被支持,提供有序、可靠、有边界的连接)
int protocol: 指定具体的协议。
通常设置为 0,表示使用给定 domain 和 type 的默认协议。
int sv[2]: 这是一个包含两个整数的数组,用于接收 socketpair 调用返回的两个相互连接的套接字文件描述符。
sv[0]: 第一个套接字的文件描述符。
sv[1]: 第二个套接字的文件描述符。
5. 返回值
6. 相似函数,或关联函数
pipe: 创建一个单向的匿名管道。socketpair 可以看作是 pipe 的双向版本。
socket / bind / connect: 用于创建和连接命名套接字(如网络套接字)。socketpair 创建的是匿名的、预先连接的套接字。
fork: 经常与 socketpair 结合使用,父进程和子进程各自保留套接字对的一端,用于彼此通信。
read / write / send / recv: 用于对 socketpair 返回的文件描述符进行数据传输。
7. 示例代码 示例 1:父子进程通过 socketpair 通信 这个经典的例子演示了如何使用 socketpair 在父进程和子进程之间建立双向通信通道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 // socketpair_fork.c// 注意:为了编译此示例,需要链接 pthread 库: gcc -o socketpair_fork socketpair_fork.c int main() { int sv& pid_t pid; char buf& ssize_t bytes_read; // 1 . 创建一对相互连接的 Unix 流式套接字 if (socketpair(AF_UNIX, SOCK_STREAM, 0 , sv) == -1 ) { perror("socketpair failed" ); exit (EXIT_FAILURE); } printf("Socket pair created: sv[0] = %d, sv[1] = %d\n" , sv& // 2 . 创建子进程 pid = fork(); if (pid == -1 ) { perror("fork failed" ); // 创建子进程失败,需要关闭已创建的套接字 close(sv& close(sv& exit (EXIT_FAILURE); } if (pid == 0 ) { // --- 子进程 --- // 关闭不需要的 sv& close(sv& // 子进程通过 sv& const char *msg_to_parent = "Hello from child process!" ; printf("Child: Sending message to parent...\n" ); if (write(sv& perror("Child: write to parent failed" ); close(sv& _exit(EXIT_FAILURE); } printf("Child: Message sent.\n" ); // 子进程通过 sv& printf("Child: Waiting for message from parent...\n" ); bytes_read = read(sv& if (bytes_read > 0 ) { buf& printf("Child: Received from parent: %s\n" , buf); } else if (bytes_read == 0 ) { printf("Child: Parent closed the connection.\n" ); } else { perror("Child: read from parent failed" ); } // 子进程完成通信,关闭其套接字 close(sv& printf("Child: Exiting.\n" ); _exit(EXIT_SUCCESS); } else { // --- 父进程 --- // 关闭不需要的 sv& close(sv& // 父进程通过 sv& printf("Parent: Waiting for message from child...\n" ); bytes_read = read(sv& if (bytes_read > 0 ) { buf& printf("Parent: Received from child: %s\n" , buf); } else if (bytes_read == 0 ) { printf("Parent: Child closed the connection.\n" ); } else { perror("Parent: read from child failed" ); } // 父进程通过 sv& const char *msg_to_child = "Hello from parent process!" ; printf("Parent: Sending message to child...\n" ); if (write(sv& perror("Parent: write to child failed" ); } else { printf("Parent: Message sent.\n" ); } // 父进程完成通信,关闭其套接字 close(sv& // 等待子进程结束 int status; if (wait(&status) == -1 ) { perror("Parent: wait for child failed" ); exit (EXIT_FAILURE); } if (WIFEXITED(status)) { printf("Parent: Child exited with status %d.\n" , WEXITSTATUS(status)); } else { printf("Parent: Child did not exit normally.\n" ); } printf("Parent: Exiting.\n" ); } return 0 ; }
代码解释:
调用 socketpair(AF_UNIX, SOCK_STREAM, 0, sv) 创建一对 Unix 域流式套接字。
调用 fork() 创建子进程。
子进程 (pid == 0):
关闭不需要的 sv[0]。
通过 sv[1] 向父进程发送一条消息。
通过 sv[1] 从父进程读取一条消息。
通信完成后,关闭 sv[1] 并退出。
父进程 (pid > 0):
示例 2:使用 socketpair 和 SOCK_DGRAM 进行有边界通信 这个例子演示了使用 SOCK_DGRAM 类型的 socketpair,它保留了消息边界。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 // socketpair_dgram.c int main() { int sv& char buf& ssize_t bytes_read; // 创建一对 Unix 数据报套接字 if (socketpair(AF_UNIX, SOCK_DGRAM, 0 , sv) == -1 ) { perror("socketpair SOCK_DGRAM failed" ); exit (EXIT_FAILURE); } printf("SOCK_DGRAM Socket pair created: sv[0] = %d, sv[1] = %d\n" , sv& // 通过 sv& const char *msg1 = "First message" ; const char *msg2 = "Second message is longer than the first one." ; printf("Sending two messages through sv[0]...\n" ); if (write(sv& perror("write msg1 failed" ); close(sv& close(sv& exit (EXIT_FAILURE); } if (write(sv& perror("write msg2 failed" ); close(sv& close(sv& exit (EXIT_FAILURE); } printf("Messages sent.\n" ); // 通过 sv& printf("Receiving messages through sv[1]...\n" ); bytes_read = read(sv& if (bytes_read > 0 ) { buf& printf("Received message 1 (%zd bytes): %s\n" , bytes_read, buf); } bytes_read = read(sv& if (bytes_read > 0 ) { buf& printf("Received message 2 (%zd bytes): %s\n" , bytes_read, buf); } // 如果再读一次,会因为没有数据而阻塞 (或返回 0 如果另一端关闭) // bytes_read = read(sv& // printf("Third read returned: %zd\n" , bytes_read); // Would block close(sv& close(sv& printf("Communication finished.\n" ); return 0 ; }
代码解释:
调用 socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) 创建一对 Unix 域数据报套接字。
通过 sv[0] 连续发送两条长度不同的消息。
通过 sv[1] 连续调用 read 两次,每次读取一条完整的消息,展示了 SOCK_DGRAM 的消息边界特性。
如果再调用一次 read,它会因为没有更多数据而阻塞(对于阻塞套接字)。
示例 3:线程间使用 socketpair 通信 这个例子演示了如何在线程之间使用 socketpair 进行通信。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #define _GNU_SOURCE #include <sys/socket.h> #include <pthread.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFER_SIZE 128 int sv[ ;2 ];void * thread_function (void *arg) { char buffer[ ;BUFFER_SIZE]; ssize_t bytes_read; printf ("Thread: Running...\n" ); const char *msg = "Message from thread" ; printf ("Thread: Sending message...\n" ); if (write (sv[ ;1 ], msg, strlen (msg)) != (ssize_t )strlen (msg)) { perror ("Thread: write failed" ); return NULL ; } printf ("Thread: Waiting for reply...\n" ); bytes_read = read (sv[ ;1 ], buffer, BUFFER_SIZE - 1 ); if (bytes_read > 0 ) { buffer[ ;bytes_read] = '\0' ; printf ("Thread: Received reply: %s\n" , buffer); } printf ("Thread: Exiting.\n" ); return NULL ; }int main () { pthread_t thread; char buffer[ ;BUFFER_SIZE]; ssize_t bytes_read; if (socketpair (AF_UNIX, SOCK_STREAM, 0 , sv) == -1 ) { perror ("socketpair failed" ); exit (EXIT_FAILURE); } printf ("Main: Socket pair created.\n" ); if (pthread_create (&thread, NULL , thread_function, NULL ) != 0 ) { perror ("pthread_create failed" ); close (sv[ ;0 ]); close (sv[ ;1 ]); exit (EXIT_FAILURE); } printf ("Main: Waiting for message from thread...\n" ); bytes_read = read (sv[ ;0 ], buffer, BUFFER_SIZE - 1 ); if (bytes_read > 0 ) { buffer[ ;bytes_read] = '\0' ; printf ("Main: Received from thread: %s\n" , buffer); } const char *reply = "Reply from main" ; printf ("Main: Sending reply...\n" ); if (write (sv[ ;0 ], reply, strlen (reply)) != (ssize_t )strlen (reply)) { perror ("Main: write reply failed" ); } if (pthread_join (thread, NULL ) != 0 ) { perror ("pthread_join failed" ); } close (sv[ ;0 ]); close (sv[ ;1 ]); printf ("Main: Exiting.\n" ); return 0 ; }
代码解释:
在 main 函数中创建 socketpair,并将返回的文件描述符存储在全局变量 sv 中。
创建一个新线程 thread。
主线程:
通过 sv[0] 从线程读取消息。
通过 sv[0] 向线程发送回复。
等待线程结束。
线程函数:
通过 sv[1] 向主线程发送消息。
通过 sv[1] 从主线程读取回复。
退出。
重要提示与注意事项: 域的选择: 在可移植代码中,应始终使用 AF_UNIX 作为 domain 参数。
类型的选择:
与 pipe 的比较:
pipe 是单向的,socketpair 是双向的。
socketpair 提供了套接字的所有功能(如 send, recv, 套接字选项等),而 pipe 只能用 read/write。
关闭: 与管道和普通套接字一样,使用完毕后需要调用 close() 关闭两端的文件描述符。
错误处理: 始终检查返回值。失败可能由资源限制或不支持的参数引起。
用途: 常用于需要双向 IPC 的场景,特别是在 fork 之后或在多线程环境中。
总结:
socketpair 是一个强大的工具,用于在同一主机上创建一对相互连接的匿名套接字。它提供了比 pipe 更灵活的双向通信能力,并且具有套接字的所有特性。理解其参数和使用方法对于实现高效的本地进程间或线程间通信非常有帮助。
https://www.calcguide.tech/2025/08/10/socketpair系统调用及示例/
https://www.calcguide.tech/2025/08/26/linux开源软件路线图/
相关阅读