网络IO模型
5个IO模型
阻塞IO,异步IO,信号驱动IO,多路复用IO,异步IO
一个IO操作的组成
(1)发起IO请求:内核接受到IO请求
(2)执行具体的IO操作:内核执行IO操作
两阶段阻塞
第一阶段:用户发起IO请求后,内核可能没有可用的数据,用户需要阻塞等待内核准备好数据
第二阶段:当内核准备好数据之后,用户进程再次阻塞,等待内核将数据返回给用户
同步和异步的区别
在于IO的第阶段是否阻塞:如果发起IO请求成功后进程阻塞直到IO完成,就是同步IO;反之,如果进程发起IO请求后可以去执行其它事,等IO完了再处理,就是异步IO。
阻塞IO和非阻塞IO的区别
在于第一步是否阻塞:当进程发起IO请求时不可读或者不可写,进程阻塞直到可读或者可写,就是阻塞IO;如果不可读或者不可写时进程返回IO失败,就是非阻塞IO。
非阻塞IO
当用户线程发起一个IO操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送IO操作。
一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么内核它马上就将数据拷贝到了用户线程,然后返回。
在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
异步IO
此时应用进程需要告知内核以下信息:
(1)数据存放位置
(2)注册回调函数,当内核IO完成后,调用回调函数通知应用进程。
我们可以简单的将异步IO理解为,用户进程向内核发送IO请求,内核收到IO请求后返回true,表示它收到用户的IO请求了,但并不立刻返回数据。用于在收到内核返回的true后,便知道内核已经收到它的IO请求了,所以此时用户就可以去做其它事情,而不必一直阻塞等待内核返回IO结果。
值得一提的是,异步IO两阶段都是不阻塞的。在第一阶段,即使内核没有数据可用,也可以直接返回给用户true,因为用户只需要知道内核准备好数据就行了。
解决阻塞IO低效率的问题
多线程+阻塞IO:每个客户端socket对应一个服务器线程,但是内存消耗比较大,服务器能管理的线程数量是有限的
IO多路复用
IO多路复用是操作系统级别的同步非阻塞IO模型,只有当被监控的socket有读写事件触发时,内核才去处理。
所谓IO多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。这种机制的使用需要额外的功能来配合: select、poll、epoll。
epoll时间复杂度O(1),epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))。
在多路复用IO模型中,会有一个内核线程不断去轮询多个socket的状态,只有当真正读写事件发生时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
信号驱动IO
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生了什么事情。
在UDP上,SIGIO信号会在下面两个事件的时候产生:
1 数据报到达套接字
2 套接字上发生错误
因此我们很容易判断SIGIO出现的时候,如果不是发生错误,那么就是有数据报到达了。
而在TCP上,由于TCP是双工的,它的信号产生过于频繁,并且信号的出现几乎没有告诉我们发生了什么事情。因此对于TCP套接字,SIGIO信号是没有什么使用的。
高性能IO模型
Proactor前摄器模式和Reactor反应器模式。两个模式不同的地方在于:Proactor用于异步IO,而Reactor用于同步IO。
在Proactor模式中:当检测到有事件发生时,会新起一个异步操作,然后交由内核线程去处理,当内核线程完成IO操作之后,发送一个通知告知操作已完成;可以得知,异步IO模型采用的就是Proactor模式。
在Reactor模式中,会先对每个client注册感兴趣的事件,然后有一个线程专门去轮询每个client是否有事件发生,当有事件发生时,便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询。多路复用IO就是采用Reactor模式。当然为了提高事件处理速度,可以通过多线程或者线程池的方式来处理事件。