Linux的五种网络IO模型


在刚开始学习到五种IO模型时,只是学习各个模型的工作特点,却不能真正的理解,类似于死记硬背,而现在伴随着知识的积累,也慢慢的将以前不懂的知识慢慢梳理,理解;今天就来聊聊,Linux下的五种IO模型。

一、同步与异步,阻塞与非阻塞

1.1 同步

同步通常是指一个任务的完成依赖于另外一个任务,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么都成功,要么都失败,两个任务的状态可保持一致。

1.2 异步

异步不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,该任务就立即执行,只要整个任务完成就算完成了。至于被依赖的任务是否完成,另一任务无法确定,所以是不可靠的任务序列。

1.3 阻塞

阻塞调用是指咋调用结果返回之前,该线程会被挂起,一直处于等待消息通知,不能执行其他任务,在得到结果后返回。

1.4 非阻塞

与阻塞等待结果的方式相反,非阻塞调用若没有在调用后立刻获得调用结果,将会立刻返回。通常非阻塞都会多次调用,所以在提高了CPU利用率的同时,增加了线程切换的次数。

二、同步阻塞与同步非阻塞

2.1 同步阻塞

线程在等待当前函数返回时,没有执行其他消息处理,而处于挂起等待状态,打个比方:

在linux中默认情况下所有的socket都是阻塞模式。当用户进程调用了read()这个系统调用,内核就开始了IO的第一个阶段:准备数据。对于网络IO来说,很多时候数据在一开始还没有到达(比如,
还没有收到一个完整的UDP包),这个时候内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户进程才解除阻塞的状态,重新运行起来;几乎所有的程序员第一次接触到的网络编程都是从listen()、read()、write() 等接口开始的,这些接口都是阻塞型的,一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。

2.2 同步非阻塞

线程在等待当前函数返回时,仍在执行其他消息处理,例如:
默认创建的socket都是阻塞的,同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK,这个可以使用ioctl()系统调用设置。这样做用户线程可以在发起IO请求后可以立即返回,如果该次读操作并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。

三、五种网络IO模型

3.1 阻塞IO模型

应用程序调用一个 IO 函数,导致应用程序阻塞,等待数据准备好。 如果数据没有准备好,一直等待…数据准备好了,从内核拷贝到用户空间,IO 函数返回成功指示。

当调用 recv()函数时,系统首先查是否有准备好的数据。如果数据没有准备好,那么系统就处于等待状态。当数据准备好后,将数据从系统缓冲区复制到用户空间,然后该函数返回。在套接应用程序中,当调用 recv()函数时,未必用户空间就已经存在数据,那么此时 recv()函数就会处于等待状态。

在这里插入图片描述

3.2非阻塞IO模型

我们把一个 SOCKET 接口设置为非阻塞就是告诉内核,当所请求的 I/O 操作无法完成时,不要将进程睡眠,而是返回一个错误。这样我们的 I/O 操作函数将不断的测试数据是否已经准备好,如果没有准备好,继续测试,直到数据准备好为止。在这个不断测试的过程中,会大量的占用 CPU 的时间。上述模型绝不被推荐。
在这里插入图片描述

3.3 IO复用模型

关于IO多路复用,简单来说,就是并非RECV函数阻塞,而是换了一个函数处于阻塞,那就是 select,poll和epoll;
select和poll类似,他们特点在于可以监听多个事件,当有时间发生,函数将会返回,然后采用轮询的方式查看具体是哪一个事件可读了,这是再调用recv函数,但是如果监听的事件过多,轮询产生的时间开销就过大,因为每一次阻塞前都要将事件的文件描述符全部从用户态拷到内核,而返回后又要拷贝到用户态所以epoll是二者的一个优化;epoll采用注册时间的机制,每监听一个事件,都会给该事件注册一个回调函数,且注册的文件描述符本身就位于内核态,所以select和poll高效;
在这里插入图片描述

3.4 信号驱动IO

两次调用,两次返回;
首先我们允许套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。
在这里插入图片描述

3.5 异步IO

相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。

Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent、libev、libuv。
在这里插入图片描述
我的其他博客关于select,poll和epoll也有详细的讲解。

猜你喜欢

转载自blog.csdn.net/weixin_45121946/article/details/106361163