用html5做的网站过程,网站的页面布局是什么,全网网络营销系统,品牌设计公司主营高级IO 1.高级IO模型基本概念1.1 阻塞IO1.2 非阻塞IO1.3 信号驱动IO1.4 IO多路转接1.5 异步IO 2. 模型代码实现2.1 非阻塞IO2.2 多路转接-selectselect函数介绍什么才叫就绪呢#xff1f;demoselect特点 2.3 多路转接-pollpoll函数介绍poll优缺点demo 2.4 多路转接-epoll… 高级IO 1.高级IO模型基本概念1.1 阻塞IO1.2 非阻塞IO1.3 信号驱动IO1.4 IO多路转接1.5 异步IO 2. 模型代码实现2.1 非阻塞IO2.2 多路转接-selectselect函数介绍什么才叫就绪呢demoselect特点 2.3 多路转接-pollpoll函数介绍poll优缺点demo 2.4 多路转接-epoll重点epoll系列的接口epoll原理demoepoll工作方式ET和LT对比ET模式和非阻塞文件描述符的关系epoll的使用场景epoll的惊群问题ET模式的demo 1.高级IO模型
非阻塞IO、记录锁、系统V流机制、IO多路转接、readv和writev函数以及存储映射IOmmap这些统称为高级IO。
任何IO过程中都包含两个步骤。第一个是等待第二个是拷贝。在实际的应用场景中等待消耗的时间往往都远高于拷贝的时间。为了让IO更高效最核心的办法就是让等待的时间尽量少。
基本概念
同步通信和异步通信
同步就是在发出一个调用时在没有得到结果之前该调用就不返回。一旦调用返回就得到返回值了。由调用者主动等待这个调用的结果。异步则相反调用在发出后这个调用就直接返回了所以没有返回结果当一个异步过程调用发出后调用者不会立刻得到结果而是在调用发出后被调用者通过状态、通知来通知调用者或者通过回调函数处理这个调用。
区分 进程同步与互斥
进程/线程同步也是进程/线程之间直接的制约关系。为了完成某种任务而建立的多个线程需要在某些位置协调他们的工作次序而等待、传递信息所产生的制约关系。
阻塞和非阻塞
关注的是程序在等待调用结果消息、返回值时的状态。阻塞调用是指在调用结果返回之前当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用是指在不能立刻得到结果之前该调用不会阻塞当前线程。
1.1 阻塞IO
在内核将数据准备好之前系统调用会一直等待。所有套接字默认都是阻塞方式。
1.2 非阻塞IO
如果内核没有将数据准备好系统调用仍然直接返回并且返回EWOULDBLOCK错误码。 非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符轮询的方式。这对CPU浪费较大。 1.3 信号驱动IO
内核将数据准备好的时候使用SIGIO信号通知应用程序进行IO操作。
1.4 IO多路转接
虽热从流程图上看起来和阻塞IO类似。实际上最核心在于多路转接能够同时等待多个IO文件描述符的就绪状态。
1.5 异步IO
由内核在数据拷贝完成时通知应用程序而信号驱动是告诉应用程序何时可以开始拷贝数据。
2. 模型代码实现
2.1 非阻塞IO
一个文件描述符默认都是阻塞IO比如说readaccept等都是一直等的
# includeunistd.h
# includefcntl.hint fcntl(int fd, int cmd, .../ *arg */);传入的cmd的值不同后面追加的参数也不相同。 fcntl函数有5个功能
复制一个现有的描述符cmdF_DUPFD获得/设置文件描述符标记cmdF_GETFD或F_SETFD获得/设置文件状态标记cmdF_GETFL或F_SETFL 获得/设置异步I/O所有权cmdF_GETOWN或F_SETOWN获得/设置记录锁cmdF_GETLK,F_SETLK或F_SETLKW
将文件描述符设置为非阻塞状态需要使用第三个个标记。如下
#includeiostream
#includeunistd.h
#include fcntl.h
#include cstring
using namespace std;void SetNonBlock(int fd)
{int f1 fcntl(fd, F_GETFL); //使用F_GETFL将当前文件描述符的属性提取出来这是一个位图if(f10){perror(fcntl);return ;}fcntl(fd, F_SETFL, f1 | O_NONBLOCK); //将该文件状态多设置一个非阻塞然后设置给fdcout set fd nonblock doneendl;
}int main()
{char buffer[1024];SetNonBlock(0);while(true){ssize_t n read(0, buffer, sizeof(buffer)-1);if(n0){buffer[n-1] 0;cout echo: bufferendl;}else if(n 0){coutread done!endl;break;}else{// 1.设置成非阻塞之后如果底层fd数据没有就绪recv/read/write/send返回值会以出错的形式返回//2. a 真的出错 b.底层没有就绪// 3.怎么区分程序是真的出错了还是fd没有就绪呢if(errnoEWOULDBLOCK){cout0 fd data not ready, try again!endl;// do_other_thing();sleep(1);}else{cerr read error, n n errno code: errno, error strstrerror(errno)endl;}}}return 0;
}2.2 多路转接-select
select函数介绍
功能系统调用select函数可以实现多路复用输入/输出模型。
select系统调用是用来让我们的程序监视多个文件描述符的状态变化的程序会停在select等待直到被监视的文件描述符有一个或者多个发生了状态改变。
#includesys/select.hint select(int nfds, fd_set *readfds, fd_set *writefds,/fd_set *exceptfds, struct timeval *timeout);参数解释
参数nfds是需要监视的最大的文件描述符1rdsetwrsetexset分别是要监视的可读、可写、异常的文件描述符的集合参数timeout为结构timeval用来设置select的等待时间
参数timeout解释
NULL表示select()没有timeoutselect会一直被阻塞直到某个文件描述符发生了事件0仅检测描述符集合的状态然后立即返回并不等待外部事件的状态。特定的时间值如果在指定的时间段里没有事件发生select将超时返回
关于fd_set位图的操作接口 **timeval结构体 ** timeval用于描述一段时间长度如果在这个时间内需要监视的文件描述符没有事件发生则函数返回返回值为0. 函数返回值
执行成功则返回文件描述符状态已改变的个数。如果返回0代表在描述符状态改变前已超过timeout时间没有返回。如果有错误发生返回-1错误原因存在errno此时参数readfdswritefdsexceptfds和timeout的值变成不可预测。 – 错误值可能是EBADF文件描述符无效或该文件已关闭。EINTR此调用被信号所中断。EINVAL参数n为负值。ENOMEM核心内存不足
什么才叫就绪呢
读就绪
socket内核中接收缓冲区的字节数大于等于低水位标记SO_RCVLOWAT。此时可以无阻塞的读该文件描述符并且返回值大于0socketTCP中对端关闭连接此时对socket读返回0监听的socket有新的连接请求socket上有未处理的错误
写就绪
socket内核中发送缓冲区中的可用字节数发送缓冲区的空闲位置大小大于等于低水位标记SO_SNDLOWAT此时可以无阻塞的写并且返回值大于0socket的写操作被关闭close或者shutdown。对一个写操作被关闭的socket进行读写会触发SIGPIPE信号。socket使用非阻塞connect连接成功或者失败之后socket有未读取的错误
异常就绪
socket上收到带外数据和TCP紧急模式相关 带外数据解释
demo
连接逻辑 使用select去等待fd的状态改变当状态改变时使用事件派发器去派发任务
使用telnet命令连接8080号端口进行发消息 源码地址
select特点
特点
可监控的文件描述符取决于sizeof(fd_set)的值。将fd加入select监控集的同时还要再使用一个数据结构array保存放到select监控集的fd。 – 一个作用是用于再select返回后array作为源数据和fd_set进行FD_ISSET判断 –另一个作用是rfds是输入输出型参数select返回后会将以前的值清空因此每次开始时需要重新设置rfds这就需要用到这个辅助数组。
缺点
每次调用select都要手动设置fd集合。麻烦每次调用select都需要把fd集合从用户态拷贝到内核态。当fd很多时候开销大。每次调用都需要在内核遍历传进来的所有fd。当fd很多时候开销大。支持的文件描述符有限。
2.3 多路转接-poll
poll函数介绍
poll函数是select的改进。
#includepoll.h
int poll(struct pollfd* fds, nfds_t nfds, int timeout);struct pollfd结构体 参数说明
fds是一个poll函数监听的结构列表。每一个元素中包含了三部分内容文件描述符监听的事件集合返回的事件集合。nfds表示fds数组的长度timeout表示poll函数超时时间单位是毫秒
注意监听设置在events里面返回的结果在revents里面查
events和revents的事件值
事件描述是否可作为输入是否可作为输出POLLIN数据包括普通数据和优先数据可读是是POLLRDNORM普通数据可读是是POLLRDBAND优先级带数据可读Linux不支持是是POLLPRI高优先级数据可读比如TCP带外数据是是POLLOUT数据包括普通数据和优先数据可写是是POLLWRNORM数据可写是是POLLWRBAND优先级带数据可读是是POLLRDHUPTCP连接被对方关闭或者对方关闭了写操作由GNU引入是是POLLERR错误否是POLLHUP挂起。比如管道的写端被关闭后读端描述符上将收到POLLHUP事件否是POLLNVAL文件描述符没有打开否是
返回结果
返回值小于0 表示出错返回值等于0表示poll函数等待超时返回值大于0 表示poll由于监听的文件描述符就绪而返回。
poll优缺点
优点 select需要使用三个指针参数poll仅仅使用一个就可以了
pollfd结构体包含了要监视的event和要发生的event不再使用值传递的方式。poll没有最大数量的限制
缺点 poll监听的文件描述符过多时
和select函数一样poll返回后需要轮询pollfd来获取就绪的描述符。每次调用pollfd都需要把大量的pollfd结构从用户态拷贝到内核中。同时连接的大量客户端在同一时刻可能只有很少的处于就绪状态因此随着监视描述符的增长其效率会线性下降。
demo
同select一样只不过修改成了poll逻辑的代码。 源码
2.4 多路转接-epoll重点
由poll改进来的兼顾多路转接的所有优点。
epoll系列的接口
//常见一个epoll的句柄
int epoll_create(int size);参数说明
size参数可忽略。使用完后必须调用close()关闭。
//epoll事件注册函数。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);参数说明
不同于select()是在监听事件时告诉内核要监听什么类型的事件而是在这里先注册要监听的事件类型。第一个参数是epoll的句柄。第二个参数表示动作三个宏来表示。 三个宏 EPOLL_CTL_ADD注册新的fd到epfd中 EPOLL_CTL_MOD修改已经注册的fd的监听事件 EPOLL_CTL_DEL从epfd中删除一个fd第三个参数是需要监听的fd。第四个参数是告诉内核需要监听什么事情
struct epoll_ecent结构体 events宏
EPOLLIN表示对应的文件描述符可以读。EPOLLOUT表示对应的文件描述符可以写。EPOLLPRI表示对应的文件描述符有紧急的数据可读这里应该表示有带外的数据到来。EPOLLERR表示对应的文件描述符发生错误
int epoll_wait(int epfd, struct epoll_event* event, int maxevents, int timeout);参数解释
参数events是分配好的epoll_event结构体数组epoll会把发生的事件赋值到events数组中event不能为空。maxevents告知内核这个events有多大这个maxevents的值不能大于创建epoll_create()时的size。timeout是超时时间毫秒0会立即返回-1是永久阻塞。如果调用成功返回准备好的文件描述符数目如果是0表示超时。小于0表示函数失败。
epoll原理 每一个epoll对象都有一个独立的eventpoll结构体用于用于存放通过epoll_ctl方法向epoll对象添加进来的事件。这些事件会挂载在红黑树中所有添加的epoll中的事件都会与设备驱动程序建立回调关系当事件就绪时会调用这个回调方法这个回调方法会将发生的事件添加到rdlist双链表就绪列表中在epoll中对于每一个事件都会建立一个epitm结构体。 当调用epoll_wait检查是否有事件发生时只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空则把发生的事件复制到用户态同时将事件数量返回给用户。(时间复杂度O(1))。
总结一下三部曲
调用epoll_create创建一个epoll句柄调用epoll_ctl将要监控的文件描述符进行注册调用epoll_wait等待文件描述符就绪
demo
epoll源码
epoll工作方式
水平触发(LT)和边缘触发(ET)
LT模式
当epoll检测到socket上事件就绪的时候可以不立刻进行处理或者只处理一部分。如缓冲区有2k数据读了1k缓冲区还剩1k第二次调用epoll_wait时epoll_wait仍然会立刻返回并通知socket读事件就绪。直到缓冲区上所有的数据都被处理完epoll_wait才不会立刻返回。支持阻塞读写和非阻塞读写 ET模式 如果在第一步将socket添加到epoll描述符的时候使用了EPOLLET标志epoll进入ET工作模式。
当epoll检测到socket上事件就绪时必须立刻处理。如缓冲区有2k数据读了1k缓冲区还剩1k的数据在第二次调用epoll_wait的时候epoll_wait不会再返回了。也就是说ET模式下文件描述符上的事件就绪后只有一次处理机会。ET的性能比LT的性能更高epoll_wait返回的次数少了很多。Ngnix默认采用ET模式使用epoll。只支持非阻塞的读写。
注意select和poll工作在LT模式epoll二者都支持
ET和LT对比
LT是epoll的默认行为。**使用ET能够减少epoll触发的次数。但是代价就是强迫程序一次响应就绪过程中就把所有的数据都处理完。**相当于一个文件描述符就绪之后不会反复被提示就绪看起来比LT更高效一些。但是在LT情况下如果也能做到每次就绪的文件描述符都立刻处理不让这个就绪被重复提示的话其实性能也是一样的。 另外ET的代码复杂度高。
ET模式和非阻塞文件描述符的关系
使用ET模式的epoll需要将文件描述符设置为非阻塞这个不是接口上的要求是工程实践的要求。
假设这样的场景服务器收到2k的请求会向客户端返回一个应答数据。如果客户端收不到应答不会发送第二个2k请求。 如果服务端写的代码是阻塞式的read并且一次只read 1k的话read不能保证一次就把所有的数据都读出来可能被信号打断剩下的9k的数据就会呆在缓冲区中。 此时由于epoll是ET模式并不会认为文件描述符就绪。epoll_wait 就不会再次返回。剩下的9k会一直在缓冲区中。直到下一次客户端再给服务器写数据。epoll_wait才能返回。
问题来了
服务器只读到1k数据要10k读完才会给客户端返回响应数据。客户端要读到服务器的响应才会发送下一个请求。客户端发送了下一个请求epoll_wait 才会返回才能去读缓冲区中剩余的数据。
所以为了解决上述问题(阻塞read不一定能一下把完整的请求读完)于是就可以使用非阻塞轮询的方式来读缓冲区保证一次能把完整的请求都读出来。 而如果是LT没这个问题因为只要缓冲区中的数据没读完就能够让epoll_wait返回文件描述符读就绪。
epoll的使用场景
epoll的高性能是有一定的特定的场景的。如果场景选择的不适宜epoll的性能可能会适得其反。
对于多链接且多链接中只用一部分连接比较活跃时比较适合使用epoll。 比如一个需要处理上万个客户端的服务器各种互联网APP的入口服务器这样的服务器很适合epoll。但是如果只是系统内部服务器和服务器间通信只有少数的几个连接这种情况下epoll就不合适。
epoll的惊群问题
该篇博客比较详细
ET模式的demo
源码地址