使用splice(2)将标准输入转发至UNIX域套接字时遇无效参数错误
问题
我需要将标准输入的所有内容复制到UNIX域套接字,想用splice(2)实现,但运行代码后报错splice: Invalid argument。以下是我的代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <string.h> #include <fcntl.h> int main(void) { int sock; struct sockaddr_un addr; const char *socket_path = "/tmp/test.sock"; int exit_status = EXIT_SUCCESS; if (socket_path == NULL) { exit(EXIT_FAILURE); } sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); exit_status = EXIT_FAILURE; goto cleanup; } memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); if (connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { perror("connect"); exit_status = EXIT_FAILURE; goto cleanup; } ssize_t bytes_spliced; /* I'm setting the offsets to NULL because they don't make sense in pipes or sockets */ while ((bytes_spliced = splice(STDIN_FILENO, NULL, sock, NULL, 16384, SPLICE_F_MORE)) > 0) { } if (bytes_spliced == -1) { perror("splice"); exit_status = EXIT_FAILURE; } cleanup: close(sock); return exit_status; }
解决方案
splice(2)的核心限制是:至少有一个文件描述符必须是管道。直接将标准输入和UNIX套接字传给splice不符合要求,因此返回EINVAL错误。
解决方法是通过管道中转数据,分两步完成splice操作:
- 将标准输入的数据转移到管道写端
- 将管道读端的数据转移到UNIX套接字
修改后的代码如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/un.h> #include <string.h> #include <fcntl.h> int main(void) { int sock; struct sockaddr_un addr; const char *socket_path = "/tmp/test.sock"; int pipe_fds[2]; int exit_status = EXIT_SUCCESS; if (socket_path == NULL) { exit(EXIT_FAILURE); } // 创建中转管道 if (pipe(pipe_fds) == -1) { perror("pipe"); exit_status = EXIT_FAILURE; goto cleanup; } sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock == -1) { perror("socket"); exit_status = EXIT_FAILURE; goto cleanup_pipe; } memset(&addr, 0, sizeof(struct sockaddr_un)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); if (connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { perror("connect"); exit_status = EXIT_FAILURE; goto cleanup_sock; } ssize_t bytes_spliced; while (1) { // 从标准输入splice到管道 bytes_spliced = splice(STDIN_FILENO, NULL, pipe_fds[1], NULL, 16384, SPLICE_F_MORE); if (bytes_spliced <= 0) { break; } // 从管道splice到套接字 bytes_spliced = splice(pipe_fds[0], NULL, sock, NULL, 16384, SPLICE_F_MORE); if (bytes_spliced == -1) { perror("splice to socket"); exit_status = EXIT_FAILURE; break; } } if (bytes_spliced == -1) { perror("splice from stdin"); exit_status = EXIT_FAILURE; } cleanup_sock: close(sock); cleanup_pipe: close(pipe_fds[0]); close(pipe_fds[1]); cleanup: return exit_status; }
关键细节
- 管道是
splice合法操作的必要条件,它作为数据中转的临时载体 SPLICE_F_MORE标志告知内核后续还有数据要发送,可优化TCP数据包的合并,减少系统调用开销- 所有打开的文件描述符都要正确关闭,避免资源泄漏
内容的提问来源于stack exchange,提问作者Runxi Yu
相关产品推荐
相关产品推荐

