1.区分堵塞、非堵塞
在一个IO操作过程中,以read为例,会涉及到两个过程:
1.等待数据准备好;
2.将数据从内核拷贝到进程中
这两个阶段是否发生阻塞,将产生不同的效果。
堵塞IO:
进程在请求read阻塞io的数据时,操作需要彻底完成后才返回到用户空间
非堵塞IO:
进程在步骤1不堵塞,如果数据没准备好,read(io操作)函数会立即返回一个状态值反馈给进程,所以对于非阻塞io,可以用一个while循环嵌套read来保证数据被读取。
2.区分同步、异步
同步IO
举个例子,同步IO在执行read函数时,如果有数据可读,进程会等待read函数执行完毕后继续执行接下去的代码。从这个层次来,阻塞IO、非阻塞IO操作、IO多路复用都是同步IO。
异步IO
异步IO操作不会导致请求的进程被blocked。当发出read请求,直接返回,等待read完成后,再通过返回值通知调用进程。
3.IO复用的定义及作用
I/O多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。常见的IO复用模型有select、poll、epoll三种,IO复用是一种同步IO。
Epoll-IO复用的优势所在:
首先介绍三种轮询方式
1.忙轮询:
假设一个进程需要监听多个IO,如果这些IO是堵塞的,那么进程在调用read函数时显然会堵塞住,这是不可取的,所以这些IO就需要是非堵塞的,进程在每个while周期内,都会轮询整个IO列表中所有的IO,如果所有IO无数据可读,直接返回,有数据读,对那个IO进行读写操作。这样每个周期内轮询所有IO的操作方式叫做忙轮询。
2.无差别轮询
当使用poll或者select监听一个队列的fd的时候,当fd处于可读或者可写状态,线程就会轮询整个队列中的fd并且取出需要被读写的fd,其他情况下,不会进行轮询,这样的轮询方式叫做无差别轮询。
3.最小时间轮询
当使用epoll监听一系列的fd的时候,如果fd处于可读或者可写状态,对应的fd会直接被放到一个ready队列中,线程不需要轮询整个队列的fd,这样的轮询方式叫做最小时间轮询。
三种模型的选择
在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调
4.epoll模型的api函数
int epoll_create(int size);
该函数返回一个文件描述符,,size参数提示内核要监听的文件描述符个数,这与内存大小有关。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
返回值:若成功,返回0,若出错返回-1。
参数:
epfd:函数epoll_create创建的句柄。
op:是指定操作类型,有一下三种
EPOLL_CTL_ADD,向epfd注册fd的event
EPOLL_CTL_MOD,修改fd已注册的event
EPOLL_CTL_DEL,从epfd上删除fd的event
fd是操作的文件描述符,vent指定内核要监听事件,它是struct epoll_event结构类型的指针
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
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);
返回值:若成功,返回就绪的文件描述符个数,若出错,返回-1,时间超时返回0
epfd:就是函数epoll_create创建的句柄。
timeout:是超时事件,-1为阻塞,0为立即返回,非阻塞,大于0是指定的微秒。
events:是一个 传入传出参数,它是epoll_event结构的指针,用来从内核得到事件的集合。
maxevents:告知内核events的大小,但不能大于epoll_create()时创建的size。
5.关于LT模式和ET模式
LT(Level Triggered,电平触发):LT模式是epoll默认的工作模式,也是select和poll的工作模式,在LT模式下,epoll相当于一个效率较高的poll。
采用LT模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理此事件,当下一次调用epoll_wait时,epoll_wait还会将此事件通告应用程序。
举个例子,当客户端给服务端发送了9个字节的数据,服务端第一次可以只读取5个字节的数据,当下一次调用epollwait时可以继续获取到fd并且读完剩下的四个字节的数据。
ET(Edge Triggered,边沿触发):当调用epoll_ctl,向参数event注册EPOLLET事件时,epoll将以ET模式来操作该文件描述符,ET模式是epoll的高效工作模式.
对于采用ET模式的文件描述符,当epoll_wait检测到其上有事件发生并将此通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不在向应用程序通知这一事件。
即当客户端给服务端发送了9个字节的数据,服务端第一次只读取5个字节的数据,当下一次调用epollwait时可以将无法获取到fd,这就意味着剩下的4个字节数据如果第一次没被完全读完就会丢失。
ET模式降低了同意epoll事件被触发的次数,效率比LT模式高。