[Netty学习笔记]一、I/O模型

冷暖自知 提交于 2020-01-30 13:47:49

I/O模型

I/O模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能。

Java共支持是三种网络编程模型:BIO/NIO/AIO

BIO模型

Java BIO:同步阻塞(传统阻塞型),服务器实现模式是一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程来进行处理。如果这个连接不做任何事情就会造成不必要的线程开销

在这里插入图片描述

其中BIO常用的编程对象即ServerSocketSocket,分别对应服务端和客户端。

需要了解的是ServerSocket#accept方法是阻塞的,没有客户端连接进来会一直阻塞在那里。

InputStream#read也是一个阻塞方法,如果没有内容可读的话,会阻塞在那里

以一个例子,来理解客户端有连接请求时,服务端会创建一个与之对应的线程,并且这个连接不做任何事情也不会被断开,会造成不必要的线程开销

/**
 * Created by myk
 * 2020/1/6 下午2:19
 * 使用BIO模型编写一个服务器端,监听6666端口,当有客户端连接时,就启动一个线程与之通讯。
 */
public class Server {

    private static int PORT = 6666;

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

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

        ServerSocket serverSocket = new ServerSocket(PORT);


        while (true) {
            Socket socket = serverSocket.accept();

            cachedThreadPool.submit(() -> {
                handler(socket);
            });

        }
    }

    private static void handler(Socket socket) {
        InputStream inputStream = null;
        try {
            inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            while (true) {
                int read = inputStream.read(bytes);
                if (read == -1) {
                    return;
                }
                System.out.println("线程信息:" + Thread.currentThread().getId() + ",名字:" + Thread.currentThread().getName());
                System.out.println(new String(bytes, 0, read));
            }
        } catch (IOException e) {
            try {
                inputStream.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }

    }


}

这里使用一个线程池,当有客户端连接到服务端时即使用一个线程来处理

客户端使用telnet来模拟

telnet 127.0.0.1 6666

联通之后进入telnet命令行,敲击enter进入编辑模式,随后输入字符串使用回车发送消息,即实现向指定端口发送消息。

在这里插入图片描述

这里每一个telnet终端即相当于一个客户端,👇下面是模拟两个客户端连接,因此开启了两个线程。但是客户端连接上即便什么都不做,也是占用了一个连接,浪费了资源,造成了不必须的线程开销

在这里插入图片描述

BIO适用场景:

适用于连接数目比较小且固定的架构,这种方式对服务端机器资源要求比较高,因为每一个连接即一个线程。(可用线程池优化)

BIO编程流程:

  1. 服务端启动一个ServerSocket
  2. 客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每一个用户建立一个线程与之通讯
  3. 客户端发出请求后,先咨询服务端是否有线程响应,如果没有则等待或这次请求被拒绝(如果是线程池模式下,建立的线程已经很多了)
  4. 如果有响应,则客户端等待服务端响应之后,可以接着再次请求

BIO存在的问题:

  1. 每个请求都需要服务端创建与之对应的独立的线程进行读写操作
  2. 如果并发数较大时,需要创建大量线程来处理连接,系统资源耗费比较大
  3. 连接建立后,如果当前线程暂时没有数据可读,则线程会阻塞在read方法那里,造成线程资源浪费
NIO模型

同步非阻塞,服务器实现模式为一个线程处理多个连接请求,即客户端发送的链接请求都会被注册到多路复用器上,多路复用器轮询到链接上有IO请求就进行处理。

在这里插入图片描述

NIO有三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)

NIO是面向缓冲区或者面向块编程的。数据读取到一个它稍后要处理的缓冲区,需要时可在缓冲区前后移动,这就增加了处理过程的灵活性,使用它可以提供非阻塞式的高伸缩性网络。

Java NIO的非阻塞模式,使一个线程从某个通道发送请求或者读取数据,但是它仅仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直到数据变的可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

综合以上,结合上图可以得知NIO是可以做到用一个线程来处理多个操作的。假设有1000个请求过来,根据实际情况可以分配5-10个线程来处理,而不像BIO那样非得分配1000个。

NIO和BIO的比较
  1. BIO以流的方式处理数据,而NIO以块的方式处理数据,块I/O的效率比流I/O高很多
  2. BIO是阻塞的,NIO是非阻塞的
  3. BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道的事件(比如:连接请求、数据读写等),因此使用单个线程就可以监听到多个客户端通道。
Java AIO
  • JDK7 引入了Asynchronous I/O,即AIO。在进行I/O编程中,常用有两种模式:Reactor和Proactor。Java的NIO就是Reactor,当有事件触发时,服务器端得到通知,进行相应的处理
  • AIO 叫做异步不阻塞的IO,AIO引入异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

BIO/AIO/NIO对比:

BIO NIO AIO
IO模型 同步阻塞 同步非阻塞(多路复用) 异步非阻塞
编程难度 简单 复杂 复杂
可靠性
吞吐量
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!