平时里用的 read()/write() 是线性 I/O 读写函数。所谓线性,就是一段缓冲区可以被连续的读写,而即将介绍的 readv()/writev() 称为向量 I/O 读写函数,也可以叫做 “分散/聚 集"(Scatter/gather) I/O 函数。这两个函数可以一次性对多个不连续的缓冲区进行读写,支持这种特性的一个关键结构是:
[C++] 纯文本查看 复制代码 struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};
它定义在 <sys/uio.h> 头文件中。readv() 和 writev() 的原型为:
[C++] 纯文本查看 复制代码 #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);
函数有 3 个参数,
第 1 个参数 fd 是一个文件描述符,如果是 readv() 则是从 fd 中读取内容存储到缓冲中;如果是 writev() 则是将缓冲中的内容写到缓冲中。
第 2 个参数 iov 是上面所提到的 struct iovec 结构指针。这个结构有两个成员,其中 iov_base 表示某个缓冲区的起始地址,iov_len 表示该段缓冲区的长度。读写多个缓冲区就是由该结构来连结的。
第 3 个参数 iovcnt 表示多少个缓冲区。
用两段简单的代码也许更容易说明这两个函数的应用:
writev.c
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/uio.h>
int main()
{
struct iovec iov[3];
ssize_t nr;
int fd, i;
char *buf[] = {".net", "groad", "www."};
fd = open ("groad.txt", O_WRONLY | O_CREAT | O_TRUNC, 0755);
if (fd == -1) {
perror ("open");
return 1;
}
/* fill out three iovec structures */
for (i = 0; i < 3; i++) {
iov[i].iov_base = buf[i];
iov[i].iov_len = strlen(buf[i]);
printf ("%d : %d\n", i, iov[i].iov_len);
}
/* write a single call, write them all out */
nr = writev (fd, iov, 3);
if (nr == -1) {
perror("writev");
return 1;
}
printf ("wrote %d bytes\n", nr);
if (close(fd)) {
perror("close");
return 1;
}
return 0;
}
这个函数将缓冲区 buf 中的内容写到 groad.txt 这个文件中,运行输出:./writev
0 : 4
1 : 5
2 : 4
wrote 13 bytes 接着,我们用 readv.c 这个程序将 groad.txt 这个文件中内容一次读出,readv.c 代码如下:
[C++] 纯文本查看 复制代码 #include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <strings.h>
int main()
{
char foo[4];
char bar[5];
char baz[4];
struct iovec iov[3];
ssize_t nr;
int fd, i;
fd = open ("groad.txt", O_RDONLY);
if (fd == -1) {
perror ("open");
return 1;
}
/* set up our iovec structures */
iov[0].iov_base = foo;
iov[0].iov_len = sizeof(foo);
iov[1].iov_base = bar;
iov[1].iov_len = sizeof(bar);
iov[2].iov_base = baz;
iov[2].iov_len = sizeof(baz);
/* read into the structures with a single call */
nr = readv (fd, iov, 3);
if (nr == -1) {
perror ("readv");
return 1;
}
for (i = 0; i < 3; i++) {
printf ("%s", (char *)iov[i].iov_base);
printf ("\n");
}
if (close(fd)) {
perror ("close");
return 1;
}
return 0;
}
运行输出:./readv
.net
groad.net
www.groad.net 向量I/O的读写的特点有:
1. 读入缓冲区的内容要依次填满缓冲区,即先填满 iov[0],接着是 iov[1], 然后是 iov[2] ... ...直到 iov[n-1] 。
2. 如果直接输出 iov[k] ,那么它会连续输出 iov[k], iov[k-1], ... ...iov[1], iov[0] 。
所以,根据第 2 条,在上面的 readv() 中输出 iov[2] 时,看上去逆序得到 www.groad.net 。
此外,向量 I/O 的一个特性是可以原子性读写,不会被其它的进程所干扰,并且它的性能比线性 I/O 要高一些,因为不必要调用多次系统调用。 |