完成了第 13 章 多种 I/O 函数

This commit is contained in:
riba2534
2019-01-26 23:06:34 +08:00
parent 2e153efab4
commit 11ad16ac8a
4 changed files with 565 additions and 1 deletions

206
README.md
View File

@@ -3382,6 +3382,7 @@ flags: 传输数据时指定的可选项信息
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
/*
成功时返回接收的字节数(收到 EOF 返回 0失败时返回 -1
sockfd: 表示数据接受对象的连接的套接字文件描述符
buf: 保存接受数据的缓冲地址值
nbytes: 可接收的最大字节数
@@ -3465,6 +3466,211 @@ send(sock, "890", strlen("890"), MSG_OOB);
![](https://i.loli.net/2019/01/26/5c4beeae46b4e.png)
TCP 数据包实际包含更多信息。TCP 头部包含如下两种信息:
- URG=1载有紧急消息的数据包
- URG指针紧急指针位于偏移量为 3 的位置。
指定 MSG_OOB 选项的数据包本身就是紧急数据包,并通过紧急指针表示紧急消息所在的位置。
紧急消息的意义在于督促消息处理,而非紧急传输形式受限的信息。
#### 13.1.4 检查输入缓冲
同时设置 MSG_PEEK 选项和 MSG_DONTWAIT 选项,以验证输入缓冲是否存在接收的数据。设置 MSG_PEEK 选项并调用 recv 函数时,即使读取了输入缓冲的数据也不会删除。因此,该选项通常与 MSG_DONTWAIT 合作,用于调用以非阻塞方式验证待读数据存与否的函数。下面的示例是二者的含义:
- [peek_recv.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch13/peek_recv.c)
- [peek_send.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch13/peek_send.c)
编译运行:
```
gcc peek_recv.c -o recv
gcc peek_send.c -o send
./recv 9190
./send 127.0.0.1 9190
```
结果:
![](https://i.loli.net/2019/01/26/5c4c0d1dc83af.png)
可以通过结果验证,仅发送了一次的数据被读取了 2 次,因为第一次调用 recv 函数时设置了 MSG_PEEK 可选项。
### 13.2 readv & writev 函数
#### 13.2.1 使用 readv & writev 函数
readv & writev 函数的功能可概括如下:
> 对数据进行整合传输及发送的函数
也就是说,通过 writev 函数可以将分散保存在多个缓冲中的数据一并发送,通过 readv 函数可以由多个缓冲分别接收。因此,适用这 2 个函数可以减少 I/O 函数的调用次数。下面先介绍 writev 函数。
```c
#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
/*
成功时返回发送的字节数,失败时返回 -1
filedes: 表示数据传输对象的套接字文件描述符。但该函数并不仅限于套接字,因此,可以像 read 一样向向其传递文件或标准输出描述符.
iov: iovec 结构体数组的地址值,结构体 iovec 中包含待发送数据的位置和大小信息
iovcnt: 向第二个参数传递数组长度
*/
```
上述第二个参数中出现的数组 iovec 结构体的声明如下:
```c
struct iovec
{
void *iov_base; //缓冲地址
size_t iov_len; //缓冲大小
};
```
下图是该函数的使用方法:
![](https://i.loli.net/2019/01/26/5c4c61b07d207.png)
writev 的第一个参数是文件描述符因此向控制台输出数据ptr 是存有待发送数据信息的 iovec 数组指针。第三个参数为 2因此从 ptr 指向的地址开始,共浏览 2 个 iovec 结构体变量,发送这些指针指向的缓冲数据。
下面是 writev 函数的使用方法:
- [writev.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch13/writev.c)
```c
#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 = 3;
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;
}
```
编译运行:
```shell
gcc writev.c -o writev
./writevi
```
结果:
```
ABC1234
Write bytes: 7
```
下面介绍 readv 函数,功能和 writev 函数正好相反.函数为:
```c
#include <sys/uio.h>
ssize_t readv(int filedes, const struct iovc *iov, int iovcnt);
/*
成功时返回接收的字节数,失败时返回 -1
filedes: 表示数据传输对象的套接字文件描述符。但该函数并不仅限于套接字,因此,可以像 read 一样向向其传递文件或标准输出描述符.
iov: iovec 结构体数组的地址值,结构体 iovec 中包含待发送数据的位置和大小信息
iovcnt: 向第二个参数传递数组长度
*/
```
下面是示例代码:
- [readv.c](https://github.com/riba2534/TCP-IP-NetworkNote/blob/master/ch13/readv.c)
```c
#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;
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;
}
```
编译运行:
```shell
gcc readv.c -o rv
./rv
```
运行结果:
![](https://i.loli.net/2019/01/26/5c4c718555398.png)
从图上可以看出,首先截取了长度为 5 的数据输出,然后再输出剩下的。
#### 13.2.2 合理使用 readv & writev 函数
实际上,能使用该函数的所有情况都适用。例如,需要传输的数据分别位于不同缓冲(数组)时,需要多次调用 write 函数。此时可通过 1 次 writev 函数调用替代操作,当然会提高效率。同样,需要将输入缓冲中的数据读入不同位置时,可以不必多次调用 read 函数,而是利用 1 次 readv 函数就能大大提高效率。
其意义在于减少数据包个数。假设为了提高效率在服务器端明确禁用了 Nagle 算法。其实 writev 函数在不采用 Nagle 算法时更有价值,如图:
![](https://i.loli.net/2019/01/26/5c4c731323e19.png)
### 13.3 基于 Windows 的实现
暂略
### 13.4 习题
> 以下答案仅代表本人个人观点,可能不是正确答案。
>
1. **下列关于 MSG_OOB 可选项的说法错误的是**
答:以下加粗的字体代表说法正确。
1. MSG_OOB 指传输 Out-of-band 数据,是通过其他路径高速传输数据
2. MSG_OOB 指通过其他路径高速传输数据,因此 TCP 中设置该选项的数据先到达对方主机
3. **设置 MSG_OOB 是数据先到达对方主机后,以普通数据的形式和顺序读取。也就是说,只是提高了传输速度,接收方无法识别这一点**
4. **MSG_OOB 无法脱离 TCP 的默认数据传输方式,即使脱离了 MSG_OOB ,也会保持原有的传输顺序。该选项只用于要求接收方紧急处理**
2. **利用 readv & writev 函数收发数据有何优点?分别从函数调用次数和 I/O 缓冲的角度给出说明**
答:需要传输的数据分别位于不同缓冲(数组)时,需要多次调用 write 函数。此时可通过 1 次 writev 函数调用替代操作,当然会提高效率。同样,需要将输入缓冲中的数据读入不同位置时,可以不必多次调用 read 函数,而是利用 1 次 readv 函数就能大大提高效率。
3. **通过 recv 函数验证输入缓冲中是否存在数据时(确认后立即返回时),如何设置 recv 函数最后一个参数中的可选项?分别说明各可选项的含义**
答:使用 MSG_PEEK 来验证输入缓冲中是否存在待接收的数据。各个可选项的意义参见上面对应章节的表格。
## 第 14 章 多播与广播