网站后台如何更改,秦皇岛网站制作报价,创作者计划,百度帐号管家通常所谓的IO#xff0c;其本质就是等待通信和进行通信#xff0c;即IO 等 拷贝。
那么想要做到高效的IO#xff0c;就要在单位时间内#xff0c;减少“等”的比重。 一.五种IO模型
阻塞 IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方…通常所谓的IO其本质就是等待通信和进行通信即IO 等 拷贝。
那么想要做到高效的IO就要在单位时间内减少“等”的比重。 一.五种IO模型
阻塞 IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式。阻塞 IO 是最常见的 IO 模型。非阻塞 IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK 错误码. 非阻塞 IO 往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对 CPU 来说是较大的浪费, 一般只有特定场景下才使用。信号驱动 IO: 内核将数据准备好的时候, 使用 SIGIO 信号通知应用程序进行 IO 操作.IO 多路转接: 和阻塞 IO 类似. 核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态。异步 IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。
此处展开分享一下非阻塞IO。 二.非阻塞IO
系统和网络中的文件描述符其默认情况下都是阻塞IO下面来看怎么将其设置为非阻塞IO。 1.fcntl函数 #include unistd.h #include fcntl.h int fcntl(int fd, int cmd, ... /* arg */ ); fcntl 函数有 5 种功能:
复制一个现有的描述符cmdF_DUPFD. 获得/设置文件描述符标记(cmdF_GETFD 或 F_SETFD). 获得/设置文件状态标记(cmdF_GETFL 或 F_SETFL). 获得/设置异步 I/O 所有权(cmdF_GETOWN 或 F_SETOWN). 获得/设置记录锁(cmdF_GETLK,F_SETLK 或 F_SETLKW).
此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞。 2.代码实现非阻塞
void SetNoBlock(int fd)
{int fl fcntl(fd, F_GETFL);if (fl 0){perror(fcntl);return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
使用 F_GETFL 将当前的文件描述符的属性取出来(这是一个位图)。
然后再使用 F_SETFL 将文件描述符设置回去. 设置回去的同时, 加上一个 O_NONBLOCK 参数表示为非阻塞。
前边提到在非阻塞IO下如果数据没有就绪那么IO就会以出错的形式返回那么如何区分到底是数据没有就绪还是真的出错了呢
通过判断errno错误码如果错误码为EWOULDBLOCK表示数据没有就绪此时可以设计程序去做其他事并通过轮询方式去检测数据是否就绪反之则为真的出错程序退出。
此外如果进程长期阻塞可能会收到系统的信号中断程序运行此时返回的错误码为EINTR所以如果不想程序被系统中断就可以通过此错误码在做判断。 三.多路转接
多路转接即等待多个fd上的新事件就绪然后通知程序员事件已经就绪可以进行IO拷贝了。 1.select
1概述
系统提供 select 函数来实现多路复用输入/输出模型。
select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的; 程序会停在 select 这里等待直到被监视的文件描述符有一个或多个发生了状态改变;
IO 等 拷贝select负责的就是等待并且是等待多个新事件的到了。 2接口 #include sys/select.h int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 参数
nfds 是需要监视的最大的文件描述符值1 rdset,wrset,exset 分别对应于需要检测的可读文件描述符的集合可写文件描述符的集合及异常文件描述符的集合是输入输出型参数。timeout 为结构体 timeval类型用来设置 select()的等待时间。
fd_set结构
这个结构就是一个整数数组, 更严格的说,是一个 位图使用位图中对应的位来表示要监视的文件描述符。
输入时比特位的位置表示文件描述符的编号比特位的内容表示是否关心该fd事件。输出时比特位的位置表示文件描述符的编号比特位的内容表示对应的fd事件是否发生。
下面是OS提供了一组操作 fd_set 的接口, 来比较方便的操作位图 void FD_CLR(int fd, fd_set *set); // 用来清除描述词组 set 中相关 fd 的位 int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组 set 中相关 fd 的位是否为真 void FD_SET(int fd, fd_set *set); // 用来设置描述词组 set 中相关 fd 的位 void FD_ZERO(fd_set *set); // 用来清除描述词组 set 的全部位 timeval结构 struct timeval { __time_t tv_sec;//秒 __suseconds_t tv_usec;//微秒 } timeval 结构用于描述一段时间长度比如5,0则表示在0-5秒内如果在这个时间内需要监视的描述符没有事件发生则函数返回返回值为 0。
参数 timeout 取值:
nullptr则表示 select没有 timeoutselect 将一直被阻塞直到某个文件描述符上发生了事件; 0仅检测描述符集合的状态然后立即返回并不等待外部事件的发生即按照非阻塞轮询的方式。 特定的时间值如果在指定的时间段里没有事件发生select 将超时返回。
函数返回值
执行成功则返回文件描述词状态已改变的个数。如果返回 0 代表在描述词状态改变前已超过 timeout 时间没有返回。当有错误发生时则返回-1错误原因存于 errno此时参数 readfdswritefds, exceptfds 和 timeout 的值变成不可预测。 3缺点
每次调用 select都需要手动设置 fd 集合, 从接口使用角度来说也非常不便。
每次调用 select都需要把 fd 集合从用户态拷贝到内核态这个开销在 fd 很多时会很大。
同时每次调用 select 都需要在内核遍历传递进来的所有 fd这个开销在 fd 很多时也很大。
select 支持的文件描述符数量太小。 2.poll
1概述
poll的作用与select完全相同也是等待多个fd等待fd上的新事件就绪随后派发事件可以理解为是select的优化版本。 2接口 #include poll.h int poll(struct pollfd *fds, nfds_t nfds, int timeout); 参数 fds是一个 poll 函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合。 nfds 表示 fds 数组的长度。 timeout以毫秒为单位设定的超时时间设为0表示非阻塞-1表示阻塞。
pollfd结构体 struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ }; 同样是位图结构short16位每一位可代表一个事件events和revents的取值: 这些事件都是宏且分别表示为不同的二进制位因此可以自由组合搭配形成事件集合。
返回值
大于0表示有几个fd就绪。等于0超时。小于0poll出错。 3优点
pollfd 结构包含了要监视的 event 和发生的 event不再使用 select“参数-值”传递的方式. 接口使用比 select 更方便。
poll 并没有最大数量限制 (但是数量过大后性能也是会下降)。 3.epoll
1概述
epoll是除了select和poll之外公认为 Linux 下性能最好的多路 I/O 就绪通知方法。 2接口
使用epoll接口需要包含头文件 #includesys/epoll.h。 int epoll_create(int size); 创建一个 epoll 的句柄. size 参数可以被忽略。用完之后, 必须调用 close()关闭。 返回值epfd供接下来的函数使用。 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epoll 的事件注册函数. 它不同于 select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型。 epoll_ctl在底层会将用户让内核关心的fd及其事件添加进由内核构成的红黑树中进行维护。 第一个参数是 epoll_create()的返回值(epoll 的句柄). 第二个参数表示动作用三个宏来表示. 第三个参数是需要监听的 fd. 第四个参数是告诉内核需要监听什么事. 第二个参数的取值: EPOLL_CTL_ADD注册新的 fd 到 epfd 中 EPOLL_CTL_MOD修改已经注册的 fd 的监听事件 EPOLL_CTL_DEL从 epfd 中删除一个 fd struct epoll_event 结构如下: typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events;/* Epoll events */ epoll_data_t data;/* User data variable */ }; 需要关注一下epoll_data_t结构体中的fd成员其要存放事件的fd当后续事件就绪时需要通过该fd来获取事件。 其中events同样为位图结构可以是以下几个宏的集合 EPOLLIN : 表示对应的文件描述符可以读 (包括对端 SOCKET 正常关闭); EPOLLOUT : 表示对应的文件描述符可以写; EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来); EPOLLERR : 表示对应的文件描述符发生错误; EPOLLHUP : 表示对应的文件描述符被挂断; EPOLLET : 将 EPOLL 设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的. EPOLLONESHOT只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个 socket 的话, 需要再次把这个 socket 加入到 EPOLL 队列里. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 收集在 epoll 监控的事件中已经发送的事件.
epoll_wait会检测内核中构成的就绪队列中是否有事件已经就绪 并将已经就绪的事件按照严格顺序放入我们定义的用户缓冲区数组中。
参数 events 是分配好的 epoll_event 结构体数组. epoll 将会把发生的事件赋值到 events 数组中 (events 不可以是空指针内核只负责把数据复制到这个 events 数组中不会去帮助我们在用户态中分配内存). maxevents 告诉内核这个 events 有多大这个 maxevents 的值不能大于创建 epoll_create()时的 size. 参数 timeout 是超时时间 (毫秒0 会立即返回-1 是永久阻塞). 如果函数调用成功返回对应 I/O 上已准备好的文件描述符数目如返回 0 表示已超时, 返回小于 0 表示函数失败.
对应的事件节点会同时包含红黑树和就绪队列两个指针从而使得该节点既可以存在于红黑树中也可以存在于就绪队列中从而无需新建新节点来进行转移。 3LT工作模式
LT即水平触发 Level Triggered 工作模式。
epoll 默认状态下就是 LT 工作模式.
当 epoll 检测到 socket 上事件就绪的时候, 可以不立刻进行处理或者只处理一部分当缓冲区还有事件未处理时epoll_wait 会不断地立刻返回并通知 socket 读事件就绪直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回。
支持阻塞读写和非阻塞读写。 4ET工作模式
ET即边缘触发 Edge Triggered 工作模式。
在第 1 步将 socket 添加到 epoll 描述符的时候使用 EPOLLET 标志, epoll 将进入 ET 工作模式。 当 epoll 检测到 socket 上事件就绪时, 必须立刻处理。如果未处理或未一次性处理完在第二次调用epoll_wait 的时候, epoll_wait 不会再返回了。 也就是说, ET 模式下, 文件描述符上的事件就绪后, 只有一次处理机会。 ET 的性能比 LT 性能更高( epoll_wait 返回的次数少了很多). Nginx 默认采用 ET 模式使用 epoll。 只支持非阻塞的读写。 LT 是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把所有的数据都处理完. 相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的. 另一方面, ET 的代码复杂程度更高。