splice不能直接连接两个socket,因其要求至少一端为pipe,而socket收发队列结构不兼容pipe的ring buffer语义,内核无法在sk_buff与socket发送队列间建立页级引用传递,故调用返回EINVAL。

不能直接用 splice 在两个 socket 之间做零拷贝转发,除非至少一个是 pipe 或支持 SPLICE_F_MOVE 的特殊文件描述符(Linux 5.19+ 有有限支持)。
为什么 splice 不能直接连接两个 socket?
Linux 的 splice 系统调用要求:一端必须是 pipe(pipefd),另一端可以是文件、socket 或另一个 pipe。它不支持 socket → socket 直连,因为内核无法在两个 socket 的缓冲区之间建立页级引用传递——socket 接收队列(sk_buff)和发送队列结构不兼容 pipe 的 ring buffer 语义。
常见错误现象:splice(fd1, nullptr, fd2, nullptr, len, SPLICE_F_MOVE) 返回 -1 并设 errno = EINVAL。
- 即使两个 socket 都是 TCP 且处于 ESTABLISHED 状态,
splice仍拒绝转发 -
sendfile同样不支持 socket 作为目标(只支持 file → socket) - 只有
socket → pipe和pipe → socket这两段组合才可行
可行方案:用 pipe 中转 + 双 splice
核心思路是引入一个内存 pipe 作为中继缓冲区,把转发拆成「socket A → pipe」和「pipe → socket B」两次 splice。这是目前最接近零拷贝的通用做法(避免用户态 memcpy,但需一次内核态 buffer 切换)。
实操要点:
- 创建 pipe:用
pipe2(&pipefd, O_CLOEXEC | O_NONBLOCK),避免阻塞和 fd 泄漏 - 第一次
splice(src_sock, nullptr, pipefd[1], nullptr, len, SPLICE_F_MOVE | SPLICE_F_NONBLOCK):从 socket 读取到 pipe 写端 - 第二次
splice(pipefd[0], nullptr, dst_sock, nullptr, len, SPLICE_F_MOVE | SPLICE_F_NONBLOCK):从 pipe 读端写入 socket - 注意返回值:可能只传输部分数据(EAGAIN),需循环或结合 epoll_wait
- pipe 容量默认 64KB(
/proc/sys/fs/pipe-max-size可调),太小会阻塞;太大浪费内存
Linux 5.19+ 的例外:AF_UNIX socket 支持 SPLICE_F_GIFT?
不是。真正的新特性是 AF_UNIX socket 的 SO_ZEROCOPY(配合 send/recv)和内核对 splice 的扩展支持——但仅限于 socket → pipe 或 pipe → socket,仍未开放 socket ↔ socket。
如果你看到某些项目声称“socket splice”,大概率是:
- 误把
sendfile(file → socket)当通用转发 - 用了 io_uring 的
IORING_OP_SPLICE,但它同样受 same constraint:目标/源之一必须是 pipe - 在 eBPF 或 XDP 层做的绕过协议栈转发(完全不走 socket API)
替代方案对比与选型建议
真要零拷贝跨 socket 转发,得跳出 splice 思维:
-
io_uring+IORING_OP_SEND/IORING_OP_RECV:可批量提交,减少 syscall 开销,但仍是 copy(除非配IORING_FEAT_FAST_POLL和IORING_SETUP_IOPOLL,且仅限特定驱动) - AF_XDP:绕过内核协议栈,直接操作网卡 ring,零拷贝但需专用网卡和用户态驱动
- DPDK / PF_RING:彻底脱离 kernel network stack,适合高性能网关,但失去 socket 语义和兼容性
- 最务实的选择:pipe +
splice+ epoll 边缘触发,99% 场景下已足够高效,且保持 POSIX 兼容
别为了“绝对零拷贝”强行拼接系统调用——pipe 的两次 splice 实际性能损失极小(无用户态内存复制,仅一次内核 buffer 引用切换),而 socket ↔ socket 的原生支持至今未进主线 kernel。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











