前言:这篇博客可能会写的有点杂乱,毕竟是想到哪里写到哪里,至少算是一个总结性的文章。
一.IO复用的生
二.IO复用实现
三.统一事件源
四.Reactor模型
五.有限状态机
一.IO复用的生
首先我们先说说不用IO复用时,我们所谓的并发服务器的极限。简单的想,我们可以用一个线程来负责一个链接,但是线程个数又不是可以无限增大的,在简单的线程间的时间调度虽然可以说很廉价,但是廉价的东西乘以庞大的基数带来的结果是昂贵的。当不活跃的链接的占用了大部分,执行效率会变得更低。
连接上的消息处理,可以分为两个阶段,即等待消息准备好和消息处理,当我们使用阻塞模型的时候,我们将这两个部分合二为一,从而一个链接只能捆绑一个链接,在需要的时候唤醒,不需要的时候睡眠。
但是如果我们把等待消息准备好和消息处理分开,我们就不用频繁的唤醒和睡眠的进程,我们这时候也就需要一个线程,它的工作就是不断询问链接消息是否准备好,一旦准备好,它就将一个链接唤醒,直接创造/拿取一个线程对其进行消息的处理,进而可以减少时间调度和不活跃链接的代价。
这就是IO多路复用了。多路复用就是处理等待消息准备好这件事的,但它可以同时处理多个连接!它也可能“等待”,所以它也会导致线程睡眠,然而这不要紧,因为它一对多、它可以监控所有连接。这样,当我们的线程被唤醒执行时,就一定是有一些连接准备好被我们的代码执行了,这是有效率的!没有那么多个线程都在争抢处理“等待消息准备好”阶段,整个世界终于清净了!
<!-- more -->
二.IO复用实现
IO复用有多种实现,但是常用的就是epoll。
-
select
select系统原型如下
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds通常要在监听的文件描述符中最大的加1,因为计数是从0开始的:-D
readfds writefds exceptfds名如其意,指的是可读,可写,异常时间对应的文件描述符集合。
select使用的轮寻模式,在开始的时候要指明监听事件,函数成功返回后会返回就绪事件。从阅读fd_set结构体代码可知,fd_set监听事件上限为1024,包含一个结构体数组,数组的元素的每一位对应一个文件描述符。
select提供了以下的函数,方便位操作
void FD_CLR(int fd, fd_set *set); //clear fd in set int FD_ISSET(int fd, fd_set *set); //judge fd is set void FD_SET(int fd, fd_set *set); //add fd into set void FD_ZERO(fd_set *set); // clear all of set
-
poll
poll原型如下
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout) struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };
看到pollfd的结构,我们就可以看出它还是轮寻模式,不过有个一好处就是他可以在监听事件不发生改变的情况下,可以不像select一样,在函数开始不断设置事件。但是他还是需要一个个访问每个监听事件,即fds的元素。
下面是events所包含的部分重要事件
POLLIN There is data to read.