重点内容
- 网络 I/O 模型、I/O 多路复用(select、poll、epoll)
- reactor 的原理与实现
- http/https 服务器的实现
- websocket 协议与服务器实现
Unix 有五种 I/O 模型:阻塞 I/O 模型(blocking I/O model)、非阻塞 I/O 模型(non-blocking model)、多路复用 I/O 模型(I/O multiplexing model)、信号驱动式 I/O 模型(signal blocking I/O model)、异步 I/O 模型(asynchronous I/O model)
阻塞与非阻塞的概念
阻塞:阻塞调用是指调用的 I/O 函数没有完成相关的功能,在调用结果返回之前,当前进程被挂起。函数只有在得到结果之后才会返回。
阻塞型 I/O 模型一般适用于单个设备的操作或者不是特别紧急传输数据时,例如:管道设备、终端设备、单客户端的网络设备,阻塞 I/O 最常用、最简单、效率也最低。
非阻塞:非阻塞调用是指当请求的 I/O 操作不能完成时,即使不能立刻返回调用结果也不会阻塞当前进程,而是立即返回。
非阻塞 I/O 模型适用于 I/O 多路复用(一个进程处理多路数据),非阻塞 I/O 防止阻塞在 I/O 上,需要轮询。
多路复用 I/O 模型
I/O 多路复用允许同时检查多个文件描述符的一种机制
-
内核添加一张表,监听表里面的信息,当有资源准备就绪,就执行
-
资源 --文件描述符去除与否
-
创建监听表
select()系统调用
系统调用 selec t会一直阻塞,直到一个或多个文件描述符集合成为就绪态。
#includeint select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds:文件描述符的范围,即所有文件描述符的最大值加 1,不能出错
readfds:监听读资源的文件描述符集合
writefds:监听写资源的文件描述符集合
exceptfds:监听异常资源的文件描述符集合
timeout:NULL 一直等待, 或者根据 struct timeval 设置等待时间的上限值
struct timeval {
long tv_sec;
long tv_usec;
};
readfds、writefds 和 exceptfds 所指向的结构体都是保存结果值的地方,在调用 select() 之前,这些参数指向的结构体必须初始化(通过 FD_ZERO() 和 FD_SET()),
返回值:成功返回已经准备就绪的文件描述符个数, 失败返回 -1
- 返回 -1 表示有错误发生
- 返回 0 表示在任何文件描述符成为就绪态之前 select() 调用已经超时
- 返回一个正整数表示有一个或多个文件描述符已经处于就绪态
通常数据类型 fd_set 以位掩码的形式来实现
void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set);
注意
select 正确返回时,会将准备好的文件描述符在集合中对应的位置置 1,其它位置全部置 0,为了保证任然可以监听其它没有 ready 的描述符,必须先将之前的集合保存下来
在 Linux 上,异常情况只会在以下两种情况下发生:
- 连接到处于信包模式下的伪终端主设备上的从设备状态发生了改变
- 流式套接字上接收到了带外数据
结构体 fd_set 的大小
linux-5.15.2includelinuxtypes.h
typedef __kernel_fd_set fd_set;
linux-5.15.2includeuapilinuxposix_types.h
#define __FD_SETSIZE 1024
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;
poll()系统调用
系统调用 poll() 执行的任务同 select() 相似,主要区别在于如何指定待检查的文件描述符
在 select() 中,提供三个集合,在每个集合中标明所需的文件描述符
poll() 管理多个文件描述符进行轮询操作(查询文件描述符,如果有指定的事件发生立刻返回),根据文件描述的状态进行处理,一般通过返回值来确定事件是否发生,没有文件描述符个数的限制
poll() 指定时间内轮询指定文件描述符,如果有指定事件发生返回一个真值
#includeint poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数 fds 列出了需要 poll() 来检查的文件描述符,该参数为 pollfd 结构体:
struct pollfd {
int fd;
short events;
short revents;
};
参数 nfds 指定了结构体 fds 中元素的个数,nfds_t 实际为无符号整型
参数 timeout 决定了 poll() 的阻塞行为:
- 如果 timeout 等于 -1,poll() 会一直阻塞直到 fds 中列出的文件描述符有一个达到就绪态(定义在对应的 events 字段中)或者捕获到一个信号
- 如果 timeout 等于 0,poll() 不会阻塞,只执行一次检查,查看哪个文件描述符处于就绪态
- 如果 timeout 大于 0,poll() 至多阻塞 timeout 毫秒,timeout 的精度受软件时钟粒度的限制
返回值:成功时 poll() 返回结构体中 events 域不为 0 的文件描述符个数,如果在超时前没有任何事件发生,poll() 会返回 0,失败返回 -1
- 注意
- select() 和 poll() 返回正整数时的细小差别,如果一个文件描述符在返回的描述符集合中出现不止一次,系统调用 select() 会将同一个描述符计数多次,而系统调用 poll() 返回的是就绪态的文件描述符个数,且一个文件描述符只会统计一次,就算在相应的 revents 字段中设定了多个位掩码也是如此



