1.概念:多个IO复用一个进程
2.IO多路复用的一般实现:
(1)创建文件描述符集合
(2)添加文件描述符到集合中
(3)通知内核开始监测
(4)根据返回的结果做对应的操作(对io读、写操作)
3.应用场景:
1. 构建并发服务器,使用IO多路复用监测多个客户端套接字
2. 使用io多路复用监测多个IO所对应的通信(如:网络、串口、can..)
3. 在阻塞io中,进行超时监测
4.IO多路复用的分类:
select,poll,epoll
IO多路复用技术(select函数模型和poll函数模型):进程通过告诉多路复用器(内核)(也就是select函数和poll函数)所有的socket号,多路复用器再去获取每一个socket的状态,当程序获取到某个socket号有事件发生了,则去该socket号上进行处理对应的事件,read事件或者是recived事件。(补充select函数与poll函数的区别是,前者底层是数组,所以有最大连接数的限制,后者是链表,无最大连接数的限制)
缺点:①同样与NIO相同,需要遍历所有socket,O(N)复杂度。②重复传递数据。因为内核是无状态的,每次都要根据进程不断重复从用户态向内核态传递所有的socket号去遍历每一个socket,获取它们的状态。浪费资源与效率,可以使用一个记事本记录每个socket的监听事件。
IO多路复用技术(epoll函数模型):epoll函数模型主要是调用了三个函数:epoll_create() , epoll_ctl() , epoll_wait();
底层流程:①通过epoll_create() 函数创建一个文件,返回一个文件描述符(Linus系统一切对象皆为文件)fd ② 创建socket接口号4,绑定socket号与端口号,监听事件,标记为非阻塞。通过epoll_ctl() 函数将该socket号 以及 需要监听的事件(如listen事件)写入fd中。③循环调用epoll_wait() 函数进行监听,返回已经就绪事件序列的长度(返回0则说明无状态,大于0则说明有n个事件已就绪)。例如如果有客户端进行连接,则,再调用accept()函数与4号socket进行连接,连接后返回一个新的socket号,且需要监听读事件,则再通过epoll_ctl()将新的socket号以及对应的事件(如read读事件)写入fd中,epoll_wait()进行监听。循环往复。
优点:不需要再遍历所有的socket号来获取每一个socket的状态,只需要管理活跃的连接。即监听在通过epoll_create()创建的文件中注册的socket号以及对应的事件。只有产生就绪事件,才会处理,所以操作都是有效的,为O(1).
补充:众所周知,设备(进程)是通过中断机制来请求CPU进行IO处理。使用epoll模型能加快CPU的处理效率。如网卡想通过IO来向系统传输一个数据,就通过中断获取CPU时间片,将该数据放置就绪事件序列中,等待CPU下一次进行epoll_wait()即可获取到对应数据,无需再通过往fd中注册socket号对应的事件等等。
epoll函数的边缘触发与水平触发的区别:
边缘触发:当文件描述符关联的读内核缓冲区发生变化时候,才发出可读信号进行通知
水平触发:只要文件描述符关联的读内核缓冲区非空,有数据可以读取,就一直发出可读信号进行通知
example:
1.读缓冲区刚开始是空的
2.读缓冲区写入2KB数据
3.水平触发和边缘触发模式此时都会发出可读信号
4.收到信号通知后,读取了1kb的数据,读缓冲区还剩余1KB数据
5.水平触发会再次进行通知,而边缘触发不会再进行通知
因为边缘触发对于一次就绪事件只会触发一次,所以需要一次性的把缓冲区的数据读完为止,也就是一直读,直到读到缓冲区为空为止,因为这一点,边缘触发需要设置文件句柄为非阻塞
epoll底层
select实现io复用的函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:监测多路IO
参数:
nfds : 关注的文件描述符中的最大值+1
readfds:关注的读事件的文件描述符集合
writefds:关注的写事件的文件描述符结合
exceptfds:其他 异常
timeout : 超时时间,如果不设置:NULL
返回值:
成功:返回到达事件的个数
失败:-1
设置了超时时间:超时时间到达但没有事件,返回0
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);
epoll函数原型:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:
epfd:文件描述符集合句柄
op:
EPOLL_CTL_ADD: 向集合中添加文件描述符
EPOLL_CTL_MOD: 修改集合
EPOLL_CTL_DEL :删除文件描述符
fd :操作的文件描述符
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 */
};
event : EPOLLIN : 读操作
EPOLLOUT : 写事件
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:监测IO事件
参数:
epfd : 文件描述符集合句柄
events : 保存到达事件的结合的首地址
maxevents : 监测时事件的个数
timeout:超时时间
-1 :不设置超时时间
返回值:
成功:返回到达事件的个数
失败:-1
设置超时:超时时间到达返回0