structiovec使用指南
一、简介
struct iovec
是一个在C语言中常用的数据结构,用于实现分散/聚集I/O。该结构体将多个缓冲区封装成一个向量,使得可以一次性地将向量中的多个缓冲区内容写入或从文件中读取。
struct iovec
由两个成员变量组成:一个指针(iov_base
)和一个长度(iov_len
)。iov_base
指向一个缓冲区的地址,iov_len
表示此缓冲区的长度。对于每个向量,可以指定多个缓冲区,每个缓冲区由一个struct iovec
结构体来表示。
使用struct iovec
的好处包括:简化代码、提高效率、兼容性强,因此值得我们在实践中深入了解。
二、聚集I/O
聚集I/O用于将多个缓冲区中的数据读取或写入到单个文件中。使用聚集I/O的好处在于可以减少系统调用的次数,提高效率。struct iovec
中的成员变量iov_base
指向一个缓冲区的地址,iov_len
表示此缓冲区的长度。对于每个向量,可以指定多个缓冲区,每个缓冲区由一个struct iovec
结构体来表示。
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
int main()
{
struct iovec iov[3];
char buf1[] = "Data from";
char buf2[] = " multiple";
char buf3[] = " buffers.";
iov[0].iov_base = buf1;
iov[0].iov_len = strlen(buf1);
iov[1].iov_base = buf2;
iov[1].iov_len = strlen(buf2);
iov[2].iov_base = buf3;
iov[2].iov_len = strlen(buf3);
ssize_t nwritten = writev(STDOUT_FILENO, iov, 3);
printf("%d bytes written\n", nwritten);
return 0;
}
在上述代码中,我们定义了3个缓冲区buf1
、buf2
、buf3
,然后将它们封装进一个向量iov
中,并使用writev
函数将向量中的所有缓冲区内容一次性地写入STDOUT_FILENO
中。
三、分散I/O
分散I/O用于从单个文件中读取数据到多个缓冲区中。使用分散I/O的好处在于可以减少系统调用的次数,提高效率。struct iovec
中的成员变量iov_base
指向一个缓冲区的地址,iov_len
表示此缓冲区的长度。对于每个向量,可以指定多个缓冲区,每个缓冲区由一个struct iovec
结构体来表示。
#include <stdio.h>
#include <sys/uio.h>
int main()
{
struct iovec iov[3];
char buf1[5];
char buf2[5];
char buf3[5];
iov[0].iov_base = buf1;
iov[0].iov_len = sizeof(buf1);
iov[1].iov_base = buf2;
iov[1].iov_len = sizeof(buf2);
iov[2].iov_base = buf3;
iov[2].iov_len = sizeof(buf3);
ssize_t nread = readv(STDIN_FILENO, iov, 3);
printf("%d bytes read: %s%s%s\n", nread, buf1, buf2, buf3);
return 0;
}
在上述代码中,我们定义了3个缓冲区buf1
、buf2
、buf3
,并将它们封装进一个向量iov
中,然后使用readv
函数将STDIN_FILENO
中的内容一次性地读取到各个缓冲区中。
四、使用结构体数组来实现分散/聚集I/O
可以使用结构体数组来实现分散/聚集I/O功能。这种方法的好处在于:可以预定义一组缓冲区并使用它们来读取和写入数据(常用于网络编程中)。
#include <stdio.h>
#include <string.h>
#include <sys/uio.h>
struct myiovec {
char *iov_base;
size_t iov_len;
};
int main()
{
struct myiovec iov[3];
char buf1[] = "Data from";
char buf2[] = " multiple";
char buf3[] = " buffers.";
iov[0].iov_base = buf1;
iov[0].iov_len = strlen(buf1);
iov[1].iov_base = buf2;
iov[1].iov_len = strlen(buf2);
iov[2].iov_base = buf3;
iov[2].iov_len = strlen(buf3);
ssize_t nwritten = writev(STDOUT_FILENO, (const struct iovec *)iov, 3);
printf("%d bytes written\n", nwritten);
return 0;
}
在上述代码中,我们定义了一个包含3个元素的结构体数组iov
,其中每个元素包含一个缓冲区的地址和它的长度。最后,我们使用writev
函数将结构体数组中的所有缓冲区内容一次性地写入STDOUT_FILENO
中。
五、使用bvec来进行流取向的I/O
分散/聚集I/O用于单个文件中的读写,而流取向的I/O用于网络编程中,实现了分散/聚集I/O在一个套接字上传输的功能。
在Linux环境下,定义了一个bvec
结构体来实现流取向的I/O功能。这个结构体的成员变量包括:bv_page
、bv_len
和bv_offset
。其中,bv_page
指向一个内存页面,bv_len
表示该页面的长度,bv_offset
表示该页面的偏移量。这些页面是用来缓存网卡收发的数据的。
#include <linux/types.h>
#include <asm/scatterlist.h>
#include <linux/highmem.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
{
struct kvec iov;
struct sock_iocb iocb;
struct iov_iter iter;
struct sg_table sg;
struct scatterlist *sgl;
unsigned int sgl_nents, offset;
int size;
sgl_nents = skb_to_sgvec(msg->msg_iter.bvec.vec.bv_page,
msg->msg_iter.bvec.vec.bv_len, 0, &sg, 1);
sgl = sg.sgl;
offset = 0;
iov.iov_len = len;
iov_iter_init(&iter, WRITE, &iov, 1, len);
while (offset < len) {
size = kernel_sendmsg(sock, msg, &iov, 1, len);
if (size < 0)
return size;
iov_iter_advance(&iter, size);
offset += size;
}
return offset;
}
在上述代码中,我们定义了一个sock_sendmsg
函数来实现流取向的I/O。其中,sgl_nents
用于设置sgl
数组的长度,sgl
数组包含指向多个页面的指针。通过在sgl
数组中设置多个页面的指针,我们可以实现多个缓冲区的读取和写入。
六、结论
使用struct iovec
可以极大地简化代码,并提高程序的效率。聚集I/O用于将多个缓冲区中的数据读取或写入到单个文件中,而使用分散I/O可以从单个文件中读取数据到多个缓冲区中。在网络编程中,我们可以使用bvec
结构体来实现流取向的I/O功能。实践中请根据自己的需求选择合适的方法。