Linux 有一个准则:一切皆文件。所以在 Linux 中,文件 I/O 函数 read 和 write 就是金字招牌,哪里都可以使用。但是在 Windows 中,文件 I/O 函数和套接字 I/O 函数需要被严格区分使用。现在将它们总结一下放到一个表格里
Windows 系统 | Linux 系统 |
---|---|
文件 I/O 函数:fopen 和 fwrite | 文件 I/O 函数:open、read 和 write |
套接字函数:send 和 recv | 套接字函数:(write 和 read) 或 (send 和 recv)或 (writev 和 readv) |
readv & writev 函数的功能概括为:对数据进行整合传输及发送。
也就是说,通过 writev 函数可以将分散保存在多个缓冲中的数据一并发送,通过 readv 函数可以由多个缓冲分别接受。因此,适当使用这 2 个函数可以减少 I/O 函数的调用次数。
writev 函数
函数原型
#include <sys/uio.h>
// 成功时返回发送的字节数,失败时返回 -1
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
上述函数参数:
- filedes:表示数据传输对象的套接字文件描述符。但该函数并不只限于套接字,因此,可以像 read 函数一样传递文件或标准输出符。
- iov:iovec 结构体数组的地址值,结构体 iovec 中包含待发送数据的位置和大小信息。
- iovcnt:向第二个参数传递的数组长度。
上述第二个参数 iov 指向的 iovec 结构体如下所示:
struct iovec {
void *iov_base; // 缓冲地址
size_t iov_len; // 缓冲大小
}
可以看到,结构体 iovec 由保存待发送数据的缓冲(char 型数组)地址值和实际发送的数据长度信息构成。我们先通过下图看一下该函数的使用方法:
writev 是向某个地方输出数据,而 filedes=1
表示向控制台输出数据(所以,如果是 readv,对应的描述符为 filedes=0
)
下面是示例代码
#include <stdio.h>
#include <sys/uio.h>
int main(int argc, char *argv[]) {
struct iovec vec[2];
char buf1[] = "ABCDEFG";
char buf2[] = "1234567";
int str_len;
vec[0].iov_base = buf1;
vec[0].iov_len = 7;
vec[1].iov_base = buf2;
vec[1].iov_len = 4;
str_len = writev(1, vec, 2);
puts("");
printf("Write bytes: %d \n", str_len);
return 0;
}
运行上述程序:
readv 函数
函数原型
#include <sys/uio.h>
// 成功时返回接受的字节数,失败时返回 -1
ssize_t readv(int fieldes, const struct iovec *iov, int iovcnt);
上述函数参数:
- filedes:表接收数据的文件(或套接字)描述符。
- iov:包含数据保存位置和大小信息的 iovec 结构体数组的地址值。
- iovcnt:第二个参数中数组长度。
下面是示例代码
#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100
int main(int argc, char *argv[]) {
struct iovec vec[2];
char buf1[BUF_SIZE] = {0, };
char buf2[BUF_SIZE] = {0, };
int str_len;
vec[0].iov_base = buf1;
vec[0].iov_len = 5; // 无论 buf1 的大小是多少,最多只能保存 5 个字节,剩余数据保存到 vec[1] 中
vec[1].iov_base = buf2;
vec[1].iov_len = BUF_SIZE;
str_len = readv(0, vec, 2);
printf("Read bytes: %d \n", str_len);
printf("First message: %s \n", buf1);
printf("Second message: %s \n", buf2);
return 0;
}
下面看一下运行结果: