网络编程中常用的分散 / 聚集 IO 技术
1. readv 与 writev:分散 / 聚集 I/O
readv(分散读)和 writev(聚集写)是用于处理分散缓冲区的 I/O 函数,核心作用是通过一次系统调用完成对多个不连续缓冲区的读写操作,减少系统调用次数(系统调用存在上下文切换开销)。
函数原型
1 |
|
关键参数
-
struct iovec描述一个缓冲区的结构,定义为:1
2
3
4struct iovec {
void *iov_base; // 缓冲区起始地址
size_t iov_len; // 缓冲区长度(字节数)
}; -
iovcnt:缓冲区数量,上限由系统限制(通常IOV_MAX,如 Linux 为 1024)。
功能与适用场景
-
readv:从文件描述符fd读取数据,按顺序填充iov数组中的缓冲区(前一个填满后再填下一个),返回实际读取的字节数。 -
writev:将iov数组中所有缓冲区的数据按顺序拼接后写入fd,返回实际写入的字节数。
网络编程场景:在构造协议报文时(如 HTTP 响应包含状态行、头部字段、正文等多个独立部分),可通过writev一次发送所有部分,避免多次write调用的开销;同理,接收报文时若需拆分到不同缓冲区(如头部和正文分离),可使用readv。
注意事项
-
若读取 / 写入中途发生错误,仍会返回已成功处理的字节数(部分成功)。
-
对于非阻塞
fd,行为与read/write一致(可能返回EAGAIN或EWOULDBLOCK)。
2. dup 与 dup2:文件描述符复制
dup 和 dup2 用于复制文件描述符,使新的描述符与原描述符指向同一个文件表项(共享文件偏移量、文件状态标志等),核心作用是实现 I/O 重定向。
函数原型
1 |
|
功能差异
-
dup:自动选择一个最小的未被使用的描述符作为新描述符,与oldfd指向同一文件。 -
dup2:显式指定新描述符newfd,若newfd已打开,则先关闭它(若newfd == oldfd,则直接返回newfd,不关闭)。
网络编程场景
-
重定向标准 I/O:例如在网络服务中,将客户端
socket的描述符通过dup2(client_fd, STDOUT_FILENO)重定向为标准输出,此时printf的内容会直接发送给客户端。 -
简化接口适配:某些库函数仅支持固定描述符(如标准输出),通过
dup2可将其适配到 socket 描述符。
注意事项
-
若
oldfd无效(未打开),两函数均返回-1并设置errno为EBADF。 -
dup2关闭newfd时若失败(如权限问题),会返回-1,但此时newfd可能已处于关闭状态,需注意错误处理。
3. sendfile:零拷贝文件传输
sendfile 用于在两个文件描述符之间直接传输数据,数据无需经过用户空间,全程在内核空间处理,属于零拷贝(zero-copy) 技术,可大幅提升大文件传输效率。
函数原型(Linux)
1 |
|
关键参数与限制
-
in_fd:源文件描述符,必须是支持mmap的常规文件(不能是 socket 或管道)。 -
out_fd:目标文件描述符,在 Linux 中必须是 socket(其他系统可能支持更多类型)。 -
offset:指定in_fd的起始读取位置,若不为NULL,传输后会更新为实际结束位置(原子操作,避免多线程竞争)。 -
count:计划传输的字节数。
功能与优势
传统文件传输流程(read + write)需要 4 次数据拷贝(磁盘→内核缓冲区→用户缓冲区→socket 缓冲区→网卡)和 2 次系统调用;而sendfile通过内核直接将文件数据从页缓存传输到 socket 缓冲区,仅需 2 次拷贝(磁盘→内核缓冲区→socket 缓冲区)和 1 次系统调用,显著减少开销。
网络编程场景:在文件服务器中(如 HTTP 服务器传输静态资源),sendfile是高效传输文件的首选方式,比read + write组合性能提升明显。
注意事项
-
跨平台差异大:Linux 的
sendfile与 BSD 的sendfile参数和行为不同,移植时需注意。 -
不支持非阻塞 I/O:若
out_fd为非阻塞 socket 且暂时无法写入,sendfile可能返回-1并设置errno为EAGAIN,但部分实现可能不支持。 -
in_fd必须是常规文件:不能用于 socket 之间的数据转发(需用splice等其他函数)。
总结
-
readv/writev:优化多缓冲区 I/O,减少系统调用次数,适合协议报文的拼接 / 拆分。 -
dup/dup2:实现文件描述符重定向,简化 I/O 接口适配。 -
sendfile:利用零拷贝技术高效传输文件,是大文件网络传输的核心优化手段。