阻塞 IO 模型
在Linux ,默认情况下所有的 socket 都是阻塞的,一个典型的读操作流程如图所示。
阻塞和非阻塞的概念描述的是用户线程调用内核 IO 操作的方式:阻塞是指 IO 操作需要彻底完成后才返回到用户空间;而非阻塞是指 IO操作被调用后立即返回给用户一个状态值,不需要等到 IO 操作彻底完成。
当应用进程调用了 recvfrom 这个系统调用后,系统内核就开始了 IO 的第一个阶段 :准备数据。
对于网络 IO 来说,很多时候数据在一开始还没到达时,系统内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。
当系统内核一直等到数据准备好了,它就会将数据从系统内核中拷贝到用户内存中,然后系统内核返回结果,用户进程才解除阻塞的状态,重新运行起来。所以,阻塞IO 模型的特点就是 IO 执行的两个阶段都被阻塞了。
大部分的 socke接口都是阻塞型的。所谓阻塞型接口是指系统调用时却不返回调用结果,并让当前线程一直处于阻塞状态,只有当该系统调用获得结果或者超时出错时才返回结果。
实际上,除非特别指定,几乎所有的 IO 接口都阻塞型的。这给网络编程带来了一个很大的问题,如在调用 send的同时,线程处于阻塞状态,则在此期间,线程将无法执行任何运算或响应任何网络请求。
非阻塞 IO 模型
在Linux 下,可以通过设置 socket IO 变为非阻塞状态。当一个非阻塞的 socket执行 read 操作时,流程如图:
当用户进程发出 read 操作时,如果内核中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个错误。
从用户进程角度讲,它发起 read 操作后,并不需要等待,而是马上就得到了一个结果当用户进程判断结果是一个错误时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。
一旦内核中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据复制到了用户内存中,然后返回正确的返回值。
所以,在非阻塞式 IO 中,用户进程其实需要不断地主动询问 kernel数据是否准备好。非阻塞的接口相比于阻塞型接口的显著差异在于被调用之后立即返回,使用如下的函数可以将某句柄归设为非阻塞状态:fcntl( fd , F_SETFL, O_NONBLOCK);
在非阻塞状态下,recv 接口在被调用后立即返回,返回值代表了不同的含义,如下所述。
recv 返回值大于 0,表示接收数据完毕,返回值即是接收到的字节数。
recv 返回 0,表示连接已经正常断开。
recv 返回 -1 ,且 errno 等于 EAGAIN ,表示 recv 操作还没执行完成。
recv 返回 -1,且 errno 不等于 EAGAIN ,表示 recv 操作遇到系统错误 errno。
可以看到服务器线程可以通过循环调用 recv 接口,可以在单个线程内实现对所有连接的数据接收。但是上述模型绝不被推荐,因为循环调用 recv将大幅度占用 CPU 使用率。
此外,在这个方案 recv 更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成”作用的接口,例如 select多路复用模式,可以次检测多个连接是存活跃。
全部0条评论
快来发表一下你的评论吧 !