我正在尝试使用拼接(人 2 拼接(将数据从 UDP 套接字直接复制到文件。 不幸的是,对splice((的第一次调用返回了EINVAL。
手册页指出:
EINVAL Target file system doesn't support splicing; target file is opened in
append mode; neither of the descriptors refers to a pipe; or offset
given for nonseekable device.
但是,我认为这些条件都不适用。 我使用的是Fedora 15(内核2.6.40-4(,所以我相信所有文件系统都支持splice((。 目标文件在第一次调用拼接时应该无关紧要,但为了完整起见,我通过 open(path, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR)
打开它。 两个调用都使用管道,并且除了 NULL 之外,两个调用都不使用偏移量。
这是我的示例代码:
int sz = splice(sock_fd, 0, mPipeFds[1], 0, 8192, SPLICE_F_MORE);
if (-1 == sz)
{
int err = errno;
LOG4CXX_ERROR(spLogger, "splice from: " << strerror(err));
return 0;
}
sz = splice(mPipeFds[0], 0, file_fd, 0, sz, SPLICE_F_MORE);
if (-1 == sz)
{
int err = errno;
LOG4CXX_ERROR(spLogger, "splice to: " << strerror(err));
}
return 0;
sock_fd由以下伪代码初始化:
int sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK);
bind(sock_fd, ...);
可能相关的是,此代码片段在 libevent 循环中运行。 libevent正在使用epoll((来确定UDP套接字是否过热。
找到了我的答案。 tl;dr - 入站端不支持 UDP。
经过足够的谷歌搜索,我偶然发现了一个论坛讨论和一些测试代码,其中打印出一个输入/输出 fd 类型及其支持的表格:
$ ./a.out
inout pipe reg chr unix tcp udp
pipe yes yes yes yes yes yes
reg yes no no no no no
chr yes no no no no no
unix no no no no no no
tcp yes no no no no no
udp no no no no no no
是的,即使在最新的内核中,也绝对不支持从 UDP 套接字读取。 对内核源代码的引用如下。
splice
调用内核中的do_splice
,内核调用 do_splice_to
,后者调用文件的file_operations
结构中的splice_read
成员。
对于套接字,该结构在 net/socket.c 中定义为socket_file_ops,这会将splice_read
字段初始化为 sock_splice_read
。
反过来,该函数包含以下代码行:
if (unlikely(!sock->ops->splice_read))
return -EINVAL;
套接字的ops
字段是一个struct proto_ops
。 对于 IPv4 UDP 套接字,它初始化为 net/ipv4/af_inet.c 中的inet_dgram_ops
。 最后,该结构没有显式初始化struct proto_ops
的splice_read
字段;即,它将其初始化为零。
所以sock_splice_read
返回 -EINVAL,然后向上传播。