【Linux网络编程学习】阻塞、非阻塞、同步、异步以及五种I/O模型

1. 基本概念

首先回顾并理解一下网络IO中阻塞、非阻塞;同步、异步的概念。

首先我们要知道:一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪”和“数据读写”。
阻塞与非阻塞是“数据就绪”阶段的概念,同步与异步是“数据读写”阶段的概念。

1.1 阻塞与非阻塞

所谓阻塞,就是IO在没有准备好数据的时候,调用IO方法的线程会被阻塞,直至收到数据时解除阻塞;而非阻塞IO在没有准备好数据的时候IO方法一样会返回,线程不会被阻塞。以recv方法为例,我们一般这样调用:

int size = recv(sockfd, buf, 1024, 0);

我们创建的sockfd默认是阻塞的,那么当执行这个函数时,如果sockfd对应的socket上数据没有就绪(即TCP接收缓存区中无数据),则该线程会阻塞,再往下的代码不会执行;但如果我们使用fcntl设置sockfd对应的socket为非阻塞,该socket工作在非阻塞状态下,那么执行该函数时,无论是否有数据准备好,该函数都会返回,然后我们可以通过返回的size值和errno来判断是否读到数据:

size == -1 && errno == EAGAIN: 表示没有读到数据,是因为非阻塞才返回;
size == 0:表示对端断开连接;
size > 0 : 表示读到数据,size为数据的长度;

1.2 同步与异步

再来说网络IO的同步和异步。刚才我们讨论阻塞和非阻塞时,关注的是数据准备阶段且数据没有准备好的情况,现在我们讨论同步和异步,关注的是数据已经准备好(TCP接收缓存区中有数据),进入数据读写阶段时的情况。

同步:同步表示A向B请求调用一个网络IO接口时,数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞)
比如调用recv方法(recv是同步IO方法)的时候其检测到数据已经准备好,然后把数据从内核的TCP接受缓存区拷贝到我们传入的buf(recv方法的第二个参数)中,这一拷贝过程耗费的是调用recv的线程的时间,直至拷贝结束,recv才返回;

异步:异步表示A向B请求调用一个网络IO接口时,向B传入请求的事件以及事件发生时通知的方式,A就可以处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。
比如某个应用程序调用了一个异步IO的API来处理数据读入(如aio_read),那么应用程序只需把需要监听的sockfd给内核传入,内核来负责数据的准备和读入。在此期间,应用程序可以做自己的事情。数据读入完毕以后,再通过信号等方式来通知应用程序。

1.3 为什么没有“异步阻塞”

把阻塞、非阻塞,同步、异步两两组合有:

  • 同步阻塞
  • 同步非阻塞
  • 异步阻塞
  • 异步非阻塞

其中,所有平时使用的Linux网络IO的API都是同步的(如write、read、recv等等),我们可以通过设置其关注的socket为阻塞或者非阻塞,这样就有了同步阻塞和同步非阻塞的API。

但是异步的API一定是非阻塞的,因为异步IO是要让内核帮我们完成数据的读写,让调用它的应用程序可以去做自己的事情,如果异步的同时又阻塞,那么应用程序无法做自己的事情,异步就失去了意义。

2. 五种IO模型

有了以上的知识铺垫,再学习Linux的五种IO模型就通畅很多。

2.1 阻塞 blocking

调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作。下图以read为例。
在这里插入图片描述

2.2 非阻塞 non-blocking

非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。非阻塞I/O执行系统调用总是立即返回,不管事件是否已经发生,若事件没有发生,则返回-1,此时可以根据 errno 区分这两种情况,对于accept,recv 和 send,事件未发生时,errno 通常被设置成 EAGAIN。
在这里插入图片描述

2.3. IO复用(IO multiplexing)

Linux 用 select/poll/epoll 函数实现 IO 复用模型,这些函数也会使进程阻塞,但是和阻塞IO所不同的是这些函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。直到有数据可读或可写时,才真正调用IO操作函数。
在这里插入图片描述

2.4 信号驱动(signal-driven)

Linux 用套接口进行信号驱动 IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO事件就绪,进程收到SIGIO 信号,然后处理 IO 事件。
在这里插入图片描述
注意区分信号驱动和异步IO的区别:信号驱动中,内核在数据准备阶段是异步,但在数据读写阶段是同步,仍然要花费应用程序自己的时间;与非阻塞IO的区别在于它提供了消息通知机制,不需要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率。

2.5 异步(asynchronous)

Linux中,可以调用 aio_read 函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。从图中可以看出,异步IO是内核负责把数据从内核空间拷贝到用户空间,不耗费应用程序自己的时间,而前面四种IO方式在数据读写阶段都需要应用程序自己负责。
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值