网络IO模型(阻塞IO、非阻塞IO、IO多路复用(select、poll、epoll、)、信号驱动IO模型、异步IO

网络IO模型

从TCP发送数据的流程说起

要深入的理解各种IO模型,那么必须先了解下产生各种IO的原因是什么,要知道这其中的本质问题那么我们就必须要知一条消息是如何从一个人发送到另外一个人的;

以两个应用程序通讯为例,我们来了解一下当“A”向"B" 发送一条消息,简单来说会经过如下流程:

第一步:应用A把消息发送到 TCP发送缓冲区。

第二步: TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器的TCP接收缓冲区。

第三步: B再从TCP接收缓冲区去读取属于自己的数据。

在这里插入图片描述

根据上图我们基本上了解消息发送要经过 应用A、应用A对应服务器的TCP发送缓冲区、经过网络传输后消息发送到了应用B对应服务器TCP接收缓冲区、然后最终应用B读取到消息。

对于一个输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被保存到内核中的缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。

阻塞IO | 非阻塞IO

我们把视角切换到上面图中的第三步, 也就是应用B从TCP缓冲区中读取数据。

在这里插入图片描述

思考一个问题:

因为应用之间发送消息是间断性的,也就是说在上图中TCP缓冲区还没有接收到属于应用B该读取的消息时,那么此时应用B向TCP缓冲区发起读取申请,TCP接收缓冲区是应该马上告诉应用B 现在没有你的数据,还是说让应用B在这里等着,直到有数据再把数据交给应用B。

把这个问题应用到第一个步骤也是一样,应用A在向TCP发送缓冲区发送数据时,如果TCP发送缓冲区已经满了,那么是告诉应用A现在没空间了,还是让应用A等待着,等TCP发送缓冲区有空间了再把应用A的数据访拷贝到发送缓冲区。

同步阻塞IO(Blocking IO)

如果上面的问题你已经思考过了,那么其实你已经明白了什么是阻塞IO了,所谓阻塞IO就是当应用B发起读取数据申请时,在内核数据没有准备好之前,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。

术语描述:在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发生错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO

流程:

1、应用进程向内核发起recfrom读取数据。

2、准备数据报(应用进程阻塞)。

3、将数据从内核负责到应用空间。

4、复制完成后,返回成功提示。

在这里插入图片描述

java代码实现

服务调用者
public class IOProducer {
    public static void main(String[] args) throws Exception {
        new Thread(() -> {
            try {
                // 连接远程服务端口
                Socket socket = new Socket("127.0.0.1", 9001);
                while (true) {
                    try {
                        // 业务逻辑
                        socket.getOutputStream().write((new Date() + ": hello server").getBytes());
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
服务提供者(BIO实现)
public class BIOConsumer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9001);
        // 循环监听
        while (true) {
            try {
                // 开启9001端口
                Socket socket = serverSocket.accept();
                // 业务处理线程
                new Thread(() -> {
                    try {
                        int len;
                        byte[] data = new byte[1024];
                        InputStream inputStream = socket.getInputStream();
                        // 阻塞读取数据
                        while ((len = inputStream.read(data)) != -1) {
                            // 业务逻辑
                            System.out.println(Thread.currentThread().getName() + ": " + new String(data, 0, len));
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

缺点

  • 只能通过开启多个线程的方式实现同时处理多个请求,并且数据没有准备好时,线程只能阻塞不能干其他事情,比较耗费资源。

同步非阻塞IO(Non-blocking IO)

按照上面的思路,所谓非阻塞IO就是当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。

术语:非阻塞IO是在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它想要的数据为止。

流程:

1、应用进程向内核发起recvfrom读取数据。

2、没有数据报准备好,即刻返回EWOULDBLOCK错误码。

3、应用进程向内核发起recvfrom读取数据。

4、已有数据包准备好就进行以下步骤,否则还是返回错误码。

5、将数据从内核拷贝到用户空间。

6、完成后,返回成功提示。

在这里插入图片描述

但是对于非阻塞IO就有一个非常严重的问题,需要不断地去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。

优点

在得到EWOULDBLOCK结果时,线程可以处理其他逻辑无须阻塞。

缺点

  • 每个socket连接都需要开个线程去处理这个fd的读写,不然如果多个fd共用同一个线程,当处理某一fd读写的时候,其他fd缓冲区有数据报准备好了也没有线程去读写

  • 当有大量客户端连接时,每次判断fd状态,都不断地去询问内核数据是否就绪(这个操作会从用户态切换到内核态),即10k个连接需要循环10k次系统调用,费事费力。

IO多路复用(IO Multiplexing)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值