参考:《UNIX 网络编程 · 卷1 : 套接字联网API》
这两个函数类似于 read 和 write 函数,但是其允许单个系统调用读入或写出多个缓冲区。这些操作分别称为分散读和集中写,因为来自读操作的输入数据被分散到多个应用缓冲区中,而来自多个应用缓冲区的输出数据集则被几种提供给单个写操作。其函数签名如下:
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
返回值:成功为读入或写出的字节数,出错为 -1 并设置相应的 errno。
这两个函数的第二个参数都是指向某个 iovec 结构的一个指针,其中 iovec 结构在头文件 <sys/uio.h> 中定义:
struct iovec
{
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
其中iov_base为缓冲区首地址,iov_len为缓冲区长度,参数 iovcnt 指定了 iovec 的个数。
iovec 结构数组中元素的数目存在某个限制,具体取决于实现,通常头文件 <sys/uio.h> 中定义 IOV_MAX 常值为 1024 个。
readv 函数则将读入的数据按上述同样顺序散布到缓冲区中。readv 总是先填满一个缓冲区,然后再填写下一个。
readv 和 writev 这两个函数可以用于任何描述符,而不仅限于套接字。另外 writev 是一个原子操作,意味着对于类似 UDP 协议,一次 writev 调用只产生单个 UDP 数据报。
之前我们说过 TCP_NODELAY 套接字选项,一个 4 字节的 write 和一个 396 字节的 write 可能触发 Nagle 算法,首选方法之一是针对这两个缓冲区调用 writev。
我们应当用尽量少的系统调用次数来完成任务。如果我们只写少量的数据,将会发现自己复制数据然后使用一次 write 会比用 writev 更合算。但也可能发现,我们管理自己的分段缓冲区会增加程序额外的复杂性成本,所以从性能成本的角度来看不合算。
实例如下:
int main(void)
{
char buf1[5], buf2[10];
struct iovec iov[2];
iov[0].iov_base = buf1;
iov[0].iov_len = 5;
iov[1].iov_base = buf2;
iov[1].iov_len = 10;
int fd = open("a.txt", O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
int rsize = readv(fd, iov, 2);
printf("rsize = %d\n", rsize);
close(fd);
fd = open("b.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd < 0)
{
perror("open");
return -1;
}
int wsize = writev(fd, iov, 2);
printf("wsize = %d\n", wsize);
close(fd);
return 0;
}