I/O多路转接服务器设计(select,poll,epoll)

I/O多路转接服务器设计(select,poll,epoll)

select和poll服务器代码:https://github.com/zzaiyuyu/select-poll

理解IO

IO就是用户向操作系统索取或发送数据的过程,linux系统实现的是缓存IO

缓存IO就是底层数据到来时,先被拷贝到内核缓冲区,然后再从内核缓冲区拷贝到程序的地址空间。

所以当调用read等系统调用,是会切换到内核态然后进行等待数据就绪,然后再将数据拷贝到进程的地址空间。

在上述等待期间,只要数据没有就绪,read调用不会返回,这中方式称为阻塞IO。文件描述符的读取默认都是阻塞形式。

下面是另外4种IO模型

非阻塞IO区别就在于等待数据时,如果没有就绪,那么read调用立刻返回错误码。这样为了保证能读到数据就得轮询,反复读取文件描述符。

IO多路转接利用select同时等待多个文件描述符,只要有一个文件描述符数据就绪,select就返回。

异步IO类似于回调机制,进程不需要等待数据就绪,内核完成数据拷贝后通知应用程序用数据就可以了。信号驱动IO是内核告诉进程何时数据已经就绪。

结论:所有IO都需要有两个步骤,内核等待数据就绪,拷贝数据到进程地址空间。高效IO就是想办法减少等待数据的时间。

阻塞和非阻塞

设置文件描述符为阻塞,那么调用read读取就会切换内核态开始等待数据就绪,只要数据没来就挂起当前进程直到有数据来才唤醒进程并返回read调用。

这样看来阻塞是在说进程等待read调用时自身的运行状态!非阻塞就是不挂起的等待调用。

异步和同步

同步与异步主要是从消息通知机制角度来说的。

比如阻塞IO,read返回后的值一定能给调用者数据或者错误信息,调用者根据返回值决定下一步改怎么做。这就是同步通信。

而异步IO,调用aio_read立即返回,不关心这个返回值,当操作系统做完数据拷贝后用信号通知进程去处理。这就是异步通信。

总结:同步是调用者主动等待调用返回值,异步是系统通知调用者调用完成了

二者的关键区别就是效率问题,同步方式总是需要检测调用结果的,很浪费资源。

https://www.jianshu.com/p/aed6067eeac9

以上是IO概念的同步异步,在进程中,同步是指各个进程执行的顺序制约关系。

select服务器的设计思路

普通服务器思路:

  1. 设置监听套接字(创建套接字,绑定地址)
  2. accept读取监听套接字,若有连接来建立连接并处理数据请求

关键在于accept读取监听套接字是阻塞的,如果没有连接来则进程会挂起。

使用select同时读取监听套接字和数据套接字,只要有一个套接字数据就绪就进行处理,这种方式就是IO多路转接,就绪事件通知机制

总结:让select进行数据等待,数据就绪后select返回。这是异步阻塞的形式。

若有某个文件描述符就绪则通知select返回,这个过程是异步的。而把timeout设为NULL是把select设为阻塞等待。

编写细节:select的参数是输入输入型,利用位图输入时让用户告诉操作系统关心哪些文件描述符上的读/写事件就绪。输出时操作系统告诉用户哪些文件描述符就绪。

epoll服务器

epoll是性能非常好的多路IO就绪事件通知机制。

使用epoll的关键步骤:

  1. 调用epoll_create创建epoll模型
  2. 调用epoll_ctl告诉内核用户关心哪些文件描述符上的事件
  3. epoll_wait是内核告诉用户哪些文件描述符就绪了,此时再进行数据操作

epoll模型是操作系统维护的一个结构,一个就绪队列,一个红黑树和一个回调机制。

使用epoll_ctl就是操纵红黑树增删改结点,结点是key-value的,用文件描述符作为key值。在有文件描述符事件就绪时,驱动的回调机制将红黑树结点放入就绪队列,同时epoll_wait发现队列不为空,则取出文件描述符进行处理。

epoll的优点——对应select,poll

select的优点

  1. 是相对于多进程多线程说的,处理多个连接请求没有进程切换带来的开销
  2. 可以做到单进程处理多连接请求。

select的缺点

  1. select设计上有同时等待文件描述符上限
  2. 在select调用前循环设置关心的文件描述符,在select返回后要循环检测哪些文件描述符就绪,这些都需要系统调用切换到内核态,随着连接数增加是有很大开销的

poll改进了select的接口,将文件描述符和关心的事件绑定到结构体里,之后定义一个全局结构体数组,对数组增删改就可以。select的输入输出型位图参数需要在调用select前重新设置,poll接口没有这个弊端。但是未解决根本问题,依旧需要循环检测。

epoll的优点

  1. 文件描述符没上限,利用红黑树结点
  2. 利用回调机制将就绪的文件描述符加入就绪队列,即使文件描述符增多,不会影响判断就绪的性能
  3. epoll_wait发现就绪队列不空则返回,此时在队列中取走已经就绪的文件描述符。不在需要循环检测,是一个O(1)操作

网上很多资料说epoll还有一个mmap内存映射,但个人认为不正确。若是直接将就绪队列映射到用户空间,那么epoll_wait将不需要传入一个buf空间去获取就绪队列内容。而且考虑安全问题,内核也不应该把就绪队列映射到用户空间。

猜你喜欢

转载自blog.csdn.net/hanzheng6602/article/details/81116087