1.Unix五种IO模型
[1] blocking IO - 阻塞IO
[2] nonblocking IO - 非阻塞IO
[3] IO multiplexing - IO多路复用
[4] signal driven IO - 信号驱动IO
[5] asynchronous IO - 异步IO
2.用户空间 / 内核空间
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
3.进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的,并且进程切换是非常耗费资源的。
4.文件描述符
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
5.缓存I/O
缓存I/O又称为标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓存中,即数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
学习了TCP和UDP的网络编程,我们理解了整个的一个编程过程及一些关键API的作用及使用。下面开始讲解IO多路复用。
学习着网络突然插入个IO多路复用,和网络有毛线的关联,蒙圈啊??为什么要用它?它又怎么用?同时在说相关联的三种机制,select ,poll,epoll。我们逐步深入,带你理清他们的产生过程。
假如现在要实现一个可以并发的网络服务器,那么我们该怎么写??前面我们说过使用多线程,多进程来实现并发,这些方法当然是可行的,但是并不是很理想,CPU的上下文切换是十分占用资源的。那么不用这种方式,一个进程要监控那么多的客户端,我们很容易想到的方法就是循环遍历每个客户端的状态。我们知道在Linux中一切皆文件,我们把每个客户端理解为一个小房子,我们轮询的去查看每个房子是不是有人,轮询一遍后,然后对有人的房子进行标记,然后再对有标记的进行处理。我们的I/O多路复用就是利用了这种思路。
先说一下IO多路复用的官方套话:通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。
按照这种思路,我们在用户态下按照这种思路也是可以实现的,我们的内核在内核态下帮我们实现了这个逻辑并且封装成了函数,就是select函数。
那么我们只要知道怎么用这个函数,就能通过一个进程实现并发服务。
那么我们通过man select指令来详细的查看一下这个函数原型及参数含义。
函数名 |
int select(int max_fd, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout); |
||||||||||
所需头文件 |
#include #include #include |
||||||||||
功能 |
监视文件描述符的变化 |
||||||||||
传入参数 |
|
||||||||||
返回值 |
成功时返回文件描述符,出错时返回为-1 |
select在监视文件描述符之前,我们还要对文件描述符进行设置,设置需要用到以下几个和select相关的宏定义
void FD_ZERO(fd_set *fdset) //从fdset中清除所有的文件描述符
void FD_SET(int fd,fd_set *fdset) //将fd加入到fdset
void FD_CLR(int fd,fd_set *fdset) //将fd从fdset里清除
int FD_ISSET(int fd,fd_set *fdset) //判断fd是否在fdset集合中
select大概理解是什么意思了,那么到底怎么用,看一个实例我们就很清楚了,假如我们的客户端做一个类似qq的东西,此时我们要接收键盘输入的信息,进行显示,同时要接受客户端的信息,我们使用select进行处理。
如下所示 :第一部分忽略,就是创建及连接的过程,第二部分开始select的应用。
(1)首先创建了rset的这莫一个集合.(bitmap 默认1024位,有1024个坑位,需要监听的fd位上置1,不监听的置0.有点像我们平时使用1个字节的不同位来表示多种状态值的操作)
(2)使用FD_ZERO清除所有的文件描述符,
(3)FD_SET将远程服务器的fd加入到集合中,
(4)select阻塞等待,这里设置的阻塞时间是5s.
当服务器发过来数据后,通过FD_ISSET来进行判断是哪个fd触发了,然后分别进行处理。
这里键盘输入的fd我们没有添加,可以通过FD_SET添加更多的文件描述符到集合中。
这里说一个selet的一个执行过程:
首先我们定义了rest文件描述符集合,程序本身执行在用户态空间,select函数直接将用户态空间的rest拷贝到
内核态,然后内核进行判断每个位是否有数据到来。(内核态进行监听效率提高)
当有数据到来的时候,内核将fd置位,1个或多个fd置位,select函数返回。
然后FD_ISSET,判断是哪个fd置位了。
形象的可以这么理解,有这么一个酒店,有1024个房间(rset),然后全部打扫干净后,准备开始营业了,此时来了5个客人,在前台录好信息,入住房间,这些人就归你这个酒店来监控管理了。早上的时候,服务员巡逻,有的客人出去了,将门口的牌子置为“请打扫房间”,然后服务员就告诉前台,有客人出去了要打扫房间,前台告诉扫地阿姨,然后扫地阿姨查看这5个房间哪个房间要扫,然后进行处理。
我们的select有几个弊端:
select是比较早(1984)实现的一种方法,有缺点,随着时间的推进,1997年实现了poll机制,优化了select的一些缺点。poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。fd_set结构体的实现原理上通过数组改为了链表来实现,这样就没有了最大连接数的限制。pollfd结构体中revents的引入,每次只重置位revents,没走的客户就不用重录信息啦。
来看一下poll的实现。
events,用户需要输入事件,可赋值如下参数
函数名 |
int poll ( struct pollfd * fds, unsigned int nfds, int timeout); |
||||||
所需头文件 |
#include |
||||||
功能 |
监视文件描述符的变化 |
||||||
传入参数 |
|
||||||
返回值 |
成功时返回fds数组中事件就绪的文件描述符的个数 |
poll的实现步骤:
(1)将需要监控的文件描述符放进fds数组中
(2)调用poll函数
(3)函数成功返回后根据返回值遍历fds数组,将关心的事件与结构体中的revents相与判断事件是否就绪。
(4)事件就绪执行相关操作。
poll机制的引入,解决了select的第一和第二个缺点,其他两个缺点仍然没有解决。
epoll,2002年实现,不用说,肯定是为了提高效率并且解决上面提到的几个弊端。
epoll是Linux目前大规模网络并发程序开发的首选模型。
epoll相关函数:
函数名 |
int epoll_create(int size); |
|||
所需头文件 |
#include |
|||
功能 |
创建一个epoll的句柄 |
|||
传入参数 |
|
|||
返回值 |
调用成功时返回一个epoll句柄描述符,失败时返回-1。 |
函数名 |
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); |
||||||||
所需头文件 |
#include |
||||||||
功能 |
注册要监听的事件类型 |
||||||||
传入参数 |
|
||||||||
返回值 |
返回0表示成功,否则返回–1 |
函数名 |
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); |
||||||||
所需头文件 |
#include |
||||||||
功能 |
等待事件的就绪 |
||||||||
传入参数 |
|
||||||||
返回值 |
成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。 |
调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝,所以不存在拷贝所带来的开销。
epoll中当有事件触发,同样也需要置位fd,但是对置位的fd进行重新排序(统一放到就绪列表中),并且epoll_wait返回值会返回一共有多少个fd触发了事件。之前阿姨打扫房间,不知道几个客人走了,也不知道位置在哪,现在会告诉你走了3个人,并且将他们三个的房间都整理到了挨着的位置,此时阿姨的效率将大大提高了。时间复杂度变为了O(1).
五.总结select poll epoll区别
网上找了两个特别形象的图,供理解:
select
epoll