一,引言
readv和writev函数的功能可以概括为:对数据进行整合传输以及发送。通过writev函数可以将分散保存在多个buff的数据一并进行发送,通过readv可以由多个buff分别接受数据,适当的使用这两个函数可以减少I/O函数的调用次数:
其实前面的例子里,如果我们能将一个请求一次性发送过去,而不是分开两部分独立发送,结果会好很多。所以,在写数据之前,将数据合并到缓冲区,批量发送出去,这是一个比较好的做法。
不过,有时候数据会存储在两个不同的缓存中,对此,我们可以使用如下的方法来进行数据的读写操作,从而避免 Nagle 算法引发的副作用。
二,writev函数 与 readv 函数
#include<sys/uio.h>
ssize_t writev(int fileds,const struct iovec* iov,int iovcnt);
这两个函数的第二个参数都是指向某个 iovec 结构数组的一个指针,其中 iovec 结构定义如下:
fields表示数据传输对象的套接字描述符、IO描述符,文件描述符等等。
iov是iovec结构体数组,iovec结构体中包含了待发送buff的指针和大小信息。
iovcnt向第二个参数传递的数组长度。
返回实际写入的字节数
iovec结构体定义如下:
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
};”
下面的程序展示了集中写的方式:
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: tcpclient <IPaddress>");
}
int socket_fd;
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
socklen_t server_len = sizeof(server_addr);
int connect_rt = connect(socket_fd, (struct sockaddr *) &server_addr, server_len);
if (connect_rt < 0) {
error(1, errno, "connect failed ");
}
char buf[128];
struct iovec iov[2];
char *send_one = "hello,";
iov[0].iov_base = send_one;
iov[0].iov_len = strlen(send_one);
iov[1].iov_base = buf;
while (fgets(buf, sizeof(buf), stdin) != NULL) {
iov[1].iov_len = strlen(buf);
int n = htonl(iov[1].iov_len);
if (writev(socket_fd, iov, 2) < 0)
error(1, errno, "writev failure");
}
exit(0);
}
24-33 行,使用了 iovec 数组,分别写入了两个不同的字符串,一个是“hello,”,另一个通过标准输入读入。在启动该程序之前,我们需要启动服务器端程序,在客户端依次输入“world”和“network”:
接下来我们可以看到服务器端接收到了 iovec 组成的新的字符串。这里的原理其实就是在调用 writev 操作时,会自动把几个数组的输入合并成一个有序的字节流,然后发送给对端。
received 12 bytes: hello,world
received 14 bytes: hello,network