服务端编程需要构建高性能的IO模型,常见的IO模型主要有以下四种
同步阻塞IO
同步非阻塞IO 默认创建的socket都是阻塞的,非阻塞IO要求socket设置为NONBLOCK
IO多路复用 经典Reactor设计模式,异步阻塞IO,select epoll
异步IO 异步非阻塞IO
同步与异步 用户线程与内核的交互方式;同步是指用户发起IO请求后,需要等待或者轮询内核IO操作完成后才能继续执行;异步是指用户线程发起IO请求后继续执行,当内核操作完成后会通知线程或者调用用户线程注册的回调函数
阻塞与非阻塞 用户线程调用内核IO操作的方式;阻塞是指IO操作需要彻底完成后才返回到用户空间,而非阻塞是指IO操作被调用后立即返回给用户一个状态值
同步阻塞IO用户线程通过系统调用read发起IO操作,由用户空间转到内核空间,内核等到数据包到达以后,将接受的数据拷贝到用户空间,完成read,用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据,整个IO请求过程中,用户线程是被阻塞的,导致用户发起请求时,不能做任何事情,对CPU资源利用不够。
同步非阻塞IO
同步非阻塞io,在同步阻塞io的基础上,将socket设置为nonblock,用户线程可以在发起io请求后立即返回;socket是非阻塞的方式,用户线程发起IO请求时立即返回,但并未读取到任何数据,用户线程需要不断发起IO请求,直到数据到达后,才真正读取到数据,继续执行;在整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求、消耗大量CPU资源,一般很少使用这种模型,而是在其他IO模型中使用非阻塞IO
IO多路复用
IO多路复用,是建立在内核上提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题;用户将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回,用户线程发起read请求,读取数据并继续执行。使用select函数进行IO请求与同步阻塞模型并无太大区别,甚至多添加监视socket,select函数额外操作,使用优势主要在于用户可以在一个线程内同时处理多个socket的IO请求,用户可以注册多个socket,然后不断调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的,同步阻塞模型中,必须使用多线程。
使用select允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的,平均时间甚至比同步阻塞IO模型还要长,IO多路复用模型使用Reactor设计模式实现了这一机制,用户线程只注册自己感兴趣的socket或者IO请求,去做自己的事情,等到数据到来时再进行处理,可以提高cpu利用率;EventHandler抽象类表示IO事件处理器,拥有IO句柄get-handle,以及对Handle的操作handle-event,继承于EventHandler的子类可以对事件处理器的行为进行定制,Reactor类用于管理EventHandler注册、删除,并使用handle-events实现事件循环,不断调用内核中的多路分离函数select,只要某个文件句柄被激活,select就返回,就会调用handle-event事件处理器进行操作。
通过reactor的方式,将用户线程轮询IO操作状态的工作交给handle-even进行处理,用户线程进行事件注册之后进行其他工作(异步),而reactor线程负责调用内核select函数,当存在socket被激活时,通知相应的用户线程,执行handle-event进行数据读取、处理工作,由于select函数是阻塞的,所以多路IO复用也被称为异步阻塞IO模型,socket是不被阻塞的,用户发起IO请求时,数据已经到达,用户线程一定不会被阻塞。其使用会阻塞线程的select系统调用,因此IO多路复用只能称为异步阻塞IO,而非真正的异步IO。