一、简介
structiovec是一个在C语言中常用的数据结构,用于实现分散/聚集I/O。该结构体将多个缓冲区封装成一个向量,使得可以一次性地将向量中的多个缓冲区内容写入或从文件中读取。
structiovec由两个成员变量组成:一个指针(iov_base)和一个长度(iov_len)。iov_base指向一个缓冲区的地址,iov_len表示此缓冲区的长度。对于每个向量,可以指定多个缓冲区,每个缓冲区由一个struct iovec结构体来表示。
使用structiovec的好处包括:简化代码、提高效率、兼容性强,因此值得我们在实践中深入了解。
二、聚集I/O
聚集I/O用于将多个缓冲区中的数据读取或写入到单个文件中。使用聚集I/O的好处在于可以减少系统调用的次数,提高效率。structiovec中的成员变量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的好处在于可以减少系统调用的次数,提高效率。structiovec中的成员变量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数组中设置多个页面的指针,我们可以实现多个缓冲区的读取和写入。
六、结论
使用structiovec可以极大地简化代码,并提高程序的效率。聚集I/O用于将多个缓冲区中的数据读取或写入到单个文件中,而使用分散I/O可以从单个文件中读取数据到多个缓冲区中。在网络编程中,我们可以使用bvec结构体来实现流取向的I/O功能。实践中请根据自己的需求选择合适的方法。