概念:

同步、异步、阻塞、非阻塞的概念

同步:所谓同步,发起一个功能调用的时候,在没有得到结果之前,该调用不返回,也就是必须一件事一件事的做,等前一件做完了,才能做下一件。

    提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事

异步:调用发出后,调用者不能立刻得到结果,而是实际处理这个调用的函数完成之后,通过状态、通知和回调来通知调用者。

  比如ajax:请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

  (在服务器处理的时候,客户端还可以干其他的事)

阻塞:指调用结果返回之前,当前线程会被挂起(CPU不给线程分配时间片),函数只能在得到结果之后才会返回。

(阻塞调用和同步调用的区别)同步调用的时候,当前线程仍然可能是激活的,只是在逻辑上当前函数没有返回。例如:在Socket中调用recv函数,如果缓冲区没有数据,这个函数会一直等待,知道数据返回。而在此时,这个线程还是可以处理其他消息的。

非阻塞:当调用后,不能直接得到结果之前,该函数不能阻塞当前线程,而是会立刻返回。

总结:

同步是指A调用了B函数,B函数需要等处理完事情才会给A返回一个结果。A拿到结果继续执行。

异步是指A调用了B函数,A的任务就完成了,去继续执行别的事了,等B处理完了事情,才会通知A。

阻塞是指,A调用了B函数,在B没有返回结果的时候,A线程被CPU挂起,不能执行任何操作(这个线程不会被分配时间片)

非阻塞是指,A调用了B函数,A不用一直等待B返回结果,可以先去干别的事。

 

Linux下的五种IO模型:

1.阻塞IO

2.非阻塞IO

3.IO复用

4.信号驱动IO

5.异步IO

 

阻塞IO模型:

linux的五种IO模型

 

 

 从上图可知,因为socket接口是阻塞型的,用户进程会调用recvfrom函数,查看内核里有没有数据报准备好,如果没有,那么只能继续等待,此时用户进程什么也不能做,一直等内核的数据报准备好了,才会将数据报从内核空间复制到用户空间里面,用户进程得到了数据,这个任务才算结束。这就是阻塞型的IO。

 

非阻塞型IO

linux的五种IO模型

 

 

 用户进程调用了recvfrom函数,向内核要数据报,内核会立刻返回一个结果,如果告诉用户进程没有数据报,那么用户进程还需要继续发送调用请求。。。知道有了数据报,然后复制到用户空间,这样就结束了调用。

非阻塞的IO可能并不会立即满足,需要应用程序调用许多次来等待操作完成。这可能效率不高,因为在很多情况下,当内核执行这个命令时,应用程序必须要进行忙碌等待,直到数据可用为止。

另一个问题,在循环调用非阻塞IO的时候,将大幅度占用CPU,所以一般使用select等来检测”是否可以操作“。

 

多路复用IO

linux的五种IO模型

 

 

 

前面说过非阻塞型IO的缺点,就是占用CPU的资源,使用select函数可以避免非阻塞IO中的轮询等待问题

linux的五种IO模型

 

 

 

可以看出用户首先要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回,当数据到达时,socket被激活,select函数返回。这时socket可读了,然后用户线程正式发起read请求,读取数据并继续执行。

这个模型在流程上和同步阻塞模型好像没有区别,甚至还需要监听socket,但使用了select以后最大的优势就是用户可以在一个线程内同时处理多个socket的IO请求,用户可以注册多个socket,然后不断的调用select读取被激活的socket,可以达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须要使用多线程,线程池技术来实现。

{

    select(socket);

    while(1) {

        sockets = select();

        for(socket in sockets) {

        if(can_read(socket)) {

            read(socket, buffer);

            process(buffer);

        }

    }

}

}                    

 

但是上面的模型仍然有很大的问题,虽然单个线程可以处理多个IO请求,但每个IO请求也是阻塞的。因此可以让用户线程注册自己感兴趣的socket或者Io请求,然后去做自己的事情,等到数据来到的时候,再进行处理

这里是使用Reactor设计模式来实现。

linux的五种IO模型

 

 通过Reactor方式,将用户线程轮询IO操作状态的工作统一交给handle_event事件循环进行处理,用户注册事件处理器之后就可以继续执行其他的工作了,而Reactor线程负责调用内核的select函数来检查socket状态。当socket被激活之后,通知响应的用户线程,执行handle_event进行数据读取。由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。

 

后面两种IO模型就先不说了。。。


 

然后来介绍java中的IO模型怎么实现。

BIO(Blocking IO)

同步阻塞IO模型,数据的读取写入必须阻塞在一个线程内等待完成。

在BIO通信模型的服务端,由一个独立的Acceptor线程负责监听客户端的连接,我们一般通过在while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。

linux的五种IO模型

 

 如上图所示,如果想要处理多个线程,则必须使用多线程,因为socket.accept()、socket.read()、socket.write()这三个函数都是同步阻塞的。

在使用了多线程之后,服务端接收到客户端的连接请求之后,会为每一个客户端创建一个新的线程进行链路处理。处理完成后,通过输出流返回应答客户端,然后线程销毁。也可以通过线程池来改善性能。利用线程池可以实现N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M)。

linux的五种IO模型

 

 Acceptor监听客户端请求,每有一个新的请求都会通过线程池创建一个新的线程,然后将socket套接字封装成一个task继承runnable,丢到线程里去执行。线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。

但问题也很明显,仍然占用了大量的资源。其底层是BIO的事实还是没有改变。

在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

public class ServerMain {

    public static void main(String[] args) throws IOException {

        //绑定端口
        ServerSocket serverSocket=new ServerSocket(3333);

        new Thread(()->{

            //accept监听
            while(true) {
                try {
                    Socket socket = serverSocket.accept();

                    //这里发生了阻塞
                    Thread.sleep(10000);

                    // 按字节流方式读取数据
                    try {
                        int len;
                        byte[] data = new byte[1024];
                        InputStream inputStream = socket.getInputStream();
                        // 按字节流方式读取数据
                        while ((len = inputStream.read(data)) != -1) {
                            System.out.println(new String(data, 0, len));
                        }
                    } catch (IOException e) {
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
ServerMain

相关文章:

  • 2021-11-07
  • 2021-10-12
  • 2021-04-10
  • 2022-01-11
猜你喜欢
  • 2021-11-15
  • 2021-05-25
  • 2021-11-17
  • 2021-09-01
  • 2021-06-22
相关资源
相似解决方案