BIO、NIO和AIO的Java实现与研究

基于Reactor的网络通信模型的相关研究

名词解释

还是先从最简单的两个内容说起:同步和异步、阻塞和非阻塞。

这两个概念在网络上可以说是千人千面,每人都能说出来个自己的理解,然后在评论区开始各种撕,在这里就简单说一下我自己的理解,如果我说的不对,那我有罪,我先说了。

同步与异步

同步和异步描述的是通信双方通信方式的差异。如果调用方发起调用后被调用方都直接返回,而在真正完成后,再通过回调函数等模式告知调用者结果,这种方式就是异步通信。反之,如果调用方发起请求后被调用方在处理完毕后再带着结果返回,这种方式称之为同步通信。

阻塞和非阻塞

阻塞和非阻塞描述的是调用者等待调用结果时的状态。如果等待时调用者处于挂起或者空转状态,而无法处理其他内容,则称这个过程是阻塞的。反之,等待时调用者仍然可以处理其他业务逻辑则为非阻塞。

小总结

可以看到的是,同步/异步和阻塞/非阻塞代表了不同内容,但是二者关注的是同一个通信过程的两个特性。一个可以明白的道理是:同步的过程中是可能存在等待的过程的,异步的过程中是不存在等待的过程的,所以根据调用方等待过程是否等待,可以将通信的过程分为三种:

  • 同步阻塞:使用同步的方式通信,且在等待过程中阻塞调用方

  • 同步非阻塞:使用同步的方式通信,但是等待过程中不阻塞调用方

  • 异步非阻塞:使用异步通信的方式通信,因为不存在等待的过程,所以只能是非阻塞的

举个不恰当的小🌰:排队去买饭,有以下几个情景

  • 排队去买饭,付了钱,后面排队去,没带手机,旁边的人也不认识,默默等了两分钟,等到了窗口前,阿姨颤抖的手给你盛了一份饭。这个过程中,结果在完全弄好了后(也就是饭),被你拿到了,这是同步的。这个等待的过程你什么别的事也没做成,这是阻塞的。所以这个过程是同步阻塞的。

  • 排队去买饭,付了钱,我不排队,我找个座玩游戏,玩一会去柜台看看我的饭出来没。在这个过程中,结果也是在完全弄好了以后,被你拿到的,这是同步的。等待的过程中,你在做别的事,这是非阻塞的。所以这个过程是同步非阻塞的。

  • 排队去买饭,付了钱,阿姨给你一个号码牌,说饭好了叫号,然后你就去玩游戏了,等饭好了,阿姨叫号,你听到了,把饭端回去吃。这个过程中,调用过程直接返回了(就是那个号码牌),这就是异步的,因为本身就没有等待的过程哦,当然你要说我可以在窗口前面等,那我也没办法哦,我认为这里就只能是非阻塞的。所以这个过程是异步非阻塞的。

网络通信模型

问题背景和初代解决方案

先提一下背景吧,我和Socket开始结缘是大三的时候,帮室友做一个物联网相关的应用项目,当时我用Java搭建了一个网站用来展示采集到的下位机的信息,并且通过Java程序将上位机的指令发送到下位机,实现相应的控制,下面是当时的网络结构图

在这里插入图片描述

在这里就第一次系统的用到了Socket通信,在这里Java程序充当了SockerServer,ESP8266(一个网络模块)充当SocketClient,那么我是怎么写的呢,我们来看下代码。

主启动类的代码

/**
 * @author mingke
 * @function 启动ServerSocket,处理客户端的请求
 * @date 2021/11/8
 * @desc IO阻塞+多线程,利用代码实现了BIO的网络模型,每个客户端都会分配一个线程来处理请求
 */
public class NormalSocketServer {
    public static long cidNum = 10;
    static int serverPort = 2021;

    public static void main(String[] args) {
        ServerSocket server = null;
        try {
            server = new ServerSocket(serverPort);
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("Server Socket has been start with port: "+serverPort);
        Socket socket = null;
        while(true) {
            try {
                //循环监听,处理客户端请求
                socket = server.accept();
                System.out.println("IP为:"+socket.getInetAddress().getHostAddress()+" 的Socket Client连接到服务器");
            }catch (Exception e) {
                e.printStackTrace();
            }
            //创建新的线程来处理客户端的请求
            NormalSocketHandler socketHandler = new NormalSocketHandler(socket);
            //启动线程
            socketHandler.start();
        }
    }
}

处理函数的代码

/**
 * @author mingke
 * @function 读写请求的处理器
 * @date 2021/11/8
 */
public class NormalSocketHandler extends Thread {
    //内部维护的Socket对象
    private NormalSocketClient socketClient;

    public NormalSocketHandler(Socket socket) {
        try {
            socketClient = new NormalSocketClient(socket);
        }catch (Exception e) {
            e.printStackTrace();
        }
        //设置全局的客户端id,方便查验
        socketClient.setCid(NormalSocketServer.cidNum++);
        //将全局id发送给Socket客户端
        try {
            socket.getOutputStream().write(Integer.valueOf(String.valueOf(socketClient.getCid())));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        super.run();
        //循环监听客户端的信息
        while((socketClient.getSocket()!=null)&&(socketClient.getSocket().isConnected())) {
            try {
                int bufferLength = socketClient.getInputStream().available();
                //如果存在数据,就进行解析
                if(bufferLength > 0) {
                    System.out.println(Thread.currentThread().getName()+"线程缓冲区大小:"+bufferLength);
                    byte bytes[] = new byte[bufferLength];
                    if(socketClient.getInputStream().read(bytes) != -1) {
                        //数据读取成功
                        System.out.println("数据内容");
                        for(int i = 0; i < bufferLength; i++) {
                            if(i == bufferLength-1) {
                                System.out.print(bytes[i] + "\n");
                            }else {
                                System.out.print(bytes[i] + " ");
                            }
                        }
                    }else {
                        //数据读取失败
                        System.out.println("数据读取失败");
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //如果客户端断开,则释放目前所用的资源
        try {
            socketClient.getInputStream().close();
            socketClient.getOutputStream().close();
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(socketClient.getCid()+"号客户端断开连接,资源释放完成");
    }
}

实体类的代码

/**
 * @author mingke
 * @function TODO
 * @date 2021/11/8
 */
public class NormalSocketClient {
    private long cid; //全局统一的ID
    private Socket socket;  //Socket
    private InputStream inputStream; //输入缓冲区
    private OutputStream outputStream;  //输出缓冲区

    // getter setter....

    //自定义构造器
    public NormalSocketClient(Socket socket) throws IOException {
        this.socket = socket;
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
    }
}

最后实现的效果就是,SocketServer会循环监听指定的服务端口,对于每一个连接到服务器的客户端,会分配一个线程处理相关的读写操作,也就是下面的逻辑

在这里插入图片描述

不妨将这个过程和第一章节里的那两个概念组合起来分析,这个过程中,每一个子线程持有一个输入缓冲区,子线程获取输入缓冲区的数据时,是数据准备好了以后才拿到的(当然要有数据发过来才算数),这个过程的通信方式是同步的,而这个过程中,我们看这段代码

//循环监听客户端的信息
while((socketClient.getSocket()!=null)&&(socketClient.getSocket().isConnected())) {
    try {
        int bufferLength = socketClient.getInputStream().available();
        //如果存在数据,就进行解析
        if(bufferLength > 0) {
            //doHandler...
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}          

在线程的run方法里面写死循环的作用还是很容易看出的,这里的程序在输入缓冲区数据没准备好时,一直处于空转状态,也就是阻塞的情况,没办法做别的事情。所以这个情况时同步阻塞的。

在讨论这些问题的时候,我看其他的程序员同志们总会提到BIO、NIO、AIO,好像不提这些就不算是懂Socket,那我就入乡随俗,目前我写的这个程序就是BIO的模式,也就是服务子线程处于同步阻塞的工作模式下。

BIO网络通信模型

服务端创建一个SocketServer,每一个客户端连接到服务器时,创建Socket并且分配一个线程进行业务逻辑处理,在连接完成后,客户端与Socket进行通信,可以实现读写的操作。当然我们可以称这种工作模式为Acceptor工作模式,在客户端较多的时候,可能需要很多线程进行处理,不过引入线程池的时候,能够缓解这个问题。

NIO网络通信模型

操作系统的开发者肯定会先于我们想到BIO存在的问题,而Java的很多设计概念又都是沿袭操作系统底层的设计,所以Java提供了一套解决方案,内置到了java.nio.*包下,它的基本思路就是,客户端连接到服务器以后,不在单独分配线程进行处理,而是借助select进行处理。那什么是select呢,可以这样简单的理解,OS在某块内存中存储了一个“key数组”,每一个接入服务器的客户端都会在数组中占有一个位置,示意图如下

在这里插入图片描述

服务端需要做的就是安排一个线程,或者是主线程去循环检测这个数组中是否有被标记的key,如果有,则取出被标记的key,因为key和客户端是一一对应的,所以,根据key得到和客户端进行交互的“通道”,完成数据通信。

再结合第一章节里面的概念进行分析,整个调用过程中结果是在数据完全准备好的之后再去读取的,所以过程是同步的,而在等待过程中,程序轮训状态,在某些客户端没有返回数据也就是等待的时候,服务端可以处理其他那些有响应的客户端的响应,所以是非阻塞,所以NIO的过程是同步非阻塞。

不过要说明的是Java里面的NIO是New IO使用选择器进行选取,但是还有一个NIO它是no-block IO也就是非阻塞的IO,它本身是没选择器的,只是一个概念,NewIO是NIO在Java里面的落地实现。

那么理论上完了,就要看一下代码是如何实现的了,首先介绍NIO里面两个新成员,通道和缓冲区

SocketChannel

Channel就类似于原先BIO中的Socket的概念,因为客户端连接到服务器以后,总归还是要进行数据传输的,NIO中使用Channel来进行数据传递,SocketChannel是一个针对Socket连接的面向流的selectable通道。一个Socket通道在执行了类方法open以后就会被创建,一个新创建的Socket通道被打开了,但是并没有连接上。如果尝试对一个没有连接的Socket连接进行IO操作将会导致一个异常–NotYetConnectedException。但是实际上,我们往往使用Socket服务端通道的accept得到一个可用的、已经连接成功的Socket通道。一个Socket通道会在执行了connect方法后完成连接;一旦连接完成,通道将会被保留,直到连接关闭,可以使用isConnected方法判断当前的Socket通道是否连接。

Socket通道支持非阻塞连接:可以创建一个套接字通道,并且可以通过 connect 方法启动建立到远程套接字的链接的过程,以便稍后通过 finishConnect 方法完成。 可以通过isConnectionPending函数来确定连接操作是否正在进行。

Socket通道支持异步终止,类似于 Channel 类中指定的异步关闭操作。 如果Socket连接的输入端因为一些原因被关闭,那么在套接字通道上的读取操作理论上将被阻塞,而实际上读取操作将完成而不读取任何字节并返回 -1。
如果Socket连接的输出端因为一些原因被关闭,那么在Socket通道上进行写入操作理论上将被阻塞,而实际上写入操作将会触发一个异常–AsynchronousCloseException。

可以使用setOptions方法来设置通道的相关参数,Socket通道有以下几个典型的参数可以进行配置

  • SO_SNDBUF: 发送缓冲区的大小

  • SO_RCVBUF: 接收缓冲区的大小

  • SO_KEEPALIVE: 是否保持连接存活

  • SO_REUSEADDR: 进行复用的服务地址

  • SO_LINGER: 如果存在数据,则在关闭时逗留(仅当配置为阻塞模式时)

  • TCP_NODELAY: 禁用Nagle算法

Buffer

Buffer是Socket连接过程中进行数据交换的场所,实际上,可以使用的Buffer有很多,因为Buffer本身是一个抽象类,而它的实现类是很丰富的,几乎涵盖了所有的数据传输方式,想系统的学习的话,可以去b站找个视频看看,我比较菜,就不展开说了,单独讲下我用的其中一个实现类ByteBuffer,也就是面向字节的Buffer。

ByteBuffer可以通过allocation函数创建出一个指定大小的Buffer,或者也可以使用wrap函数将一个byte数组包装成一个Buffer,其实也很好选择,如果要进行数据的读入,一般通过allocation创建,如果要写入数据,则使用wrap,好吧,这里有点废话文学了。

那我就简单说说ByteBuffer的使用,毕竟目前为止,我只用过这个,首先来认识几个成员变量。

  • position: 下一个读/写的指针位置

  • limit: 读入模式下,代表缓冲区中消息内容的大小

  • capacity: 缓冲区的大小

在设计的时候,ByteBuffer就是读写复用的,在使用时可以通过flip函数进行读写模式的切换,下面的图解描述了这几个变量在读写的时候的区别。

在这里插入图片描述

整体思路与代码实现

目前来看的话,思路是有的。

  • 将客户端的行为检测注册到系统

  • 系统通过select轮询检查

  • 处理被标记的key并复位

那么我们使用NIO对初代的代码进行迭代,首先是服务端的代码。

/**
 * @author mingke
 * @function TODO
 * @date 2021/11/8
 * @desc IO多路复用+单线程轮询,利用NIO实现,也就是非阻塞型IO模型
 */
public class NIOSocketServer {
    static Integer cidNum = 10;
    static int serverPort = 2021;

    public static void main(String[] args) throws IOException {
        //用于处理请求的线程池
        final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(20, 1000, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000));
        //服务端的Socket处理通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //给服务通道设置监听的端口
        ssc.socket().bind(new InetSocketAddress(serverPort),1000);

        //设置Selector
        Selector selector = Selector.open();
        System.out.println("Server Socket has been start with port: "+serverPort);
        ssc.configureBlocking(false); //必须设置成非阻塞
        ssc.register(selector, SelectionKey.OP_ACCEPT); //目前serverSocket只关心accept

        while(true) {
            //Future检查是否有标记为被修改
            int flag = selector.select();
            if(flag == 0) {
                continue; //没有的话,就继续检查
            }
            //如果有的话,就得到目前被标记的keys
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> it = selectionKeys.iterator();

            //迭代访问目前被标记的key
            while(it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();  //移除key,避免重复判断
                if(key.isAcceptable()) {
                    //原来是等待链接才会阻塞,而这里是已经要链接了,就算阻塞了也会马上往下走
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false); //设置成非阻塞才能监听
                    //此时Selector关心的是是否有数据打过来
                    sc.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(512) );
                    System.out.println("new connection");
                }
                if(key.isReadable()) {
                    //如果是写操作,则可以通过通道得到缓冲区,然后获取到信息
                    SocketChannel clientSocketChannel = (SocketChannel)key.channel();
                    //System.out.println("socket readable");
                    if(!clientSocketChannel.isConnected()) {
                        clientSocketChannel.finishConnect();
                        key.cancel();
                        clientSocketChannel.close();
                        System.out.println("socket closed2");
                        continue;
                    }
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    int len = clientSocketChannel.read(buffer);
                    Socket socket = clientSocketChannel.socket();

                    if(len == -1) {
                        clientSocketChannel.finishConnect();
                        key.cancel();
                        clientSocketChannel.close();
                        System.out.println("socket closed1");
                    } else {
                        threadPool.execute(new NIOSocketHandler(clientSocketChannel, buffer));
                    }
                }
            }
        }
    }
}

然后是处理函数的代码

/**
 * @author mingke
 * @function 读入/写出处理函数
 * @date 2021/11/8
 */
public class NIOSocketHandler extends Thread{
    //处理器持有一个客户端对象
    private NIOSocketClient socketClient;

    //自定义的处理函数
    public NIOSocketHandler(SocketChannel channel, ByteBuffer byteBuffer) throws IOException {
        socketClient = new NIOSocketClient(channel, byteBuffer);
        //设置客户端全局统一的ID
        socketClient.setCid(NIOSocketServer.cidNum++);

    }

    @Override
    public void run() {
        super.run();
        //设置为读入模式
        socketClient.getByteBuffer().flip();
        //获取到当前的长度
        int bufferLength = socketClient.getByteBuffer().limit();
        //设置缓冲数组
        byte bytes[] = new byte[bufferLength];
        //读入数据到缓冲数组
        socketClient.getByteBuffer().get(bytes, 0, bufferLength);
        System.out.println("数据长度为:"+bufferLength+" 数据内容如下");
        for(int i = 0; i < bufferLength; i++) {
            if(i == bufferLength-1) {
                System.out.print(bytes[i] + "\n");
            }else {
                System.out.print(bytes[i] + " ");
            }
        }
        //清除残余无用数据
        socketClient.getByteBuffer().clear();
    }
}

客户端实体类的部分代码

/**
 * @author mingke
 * @function 客户端实体类
 * @date 2021/11/8
 */
public class NIOSocketClient {
    private Integer cid; //全局统一的ID
    private SocketChannel socketChannel;
    private ByteBuffer byteBuffer;

    //geter、setter...

    //自定义构造器
    public NIOSocketClient(SocketChannel socketChannel, ByteBuffer byteBuffer) {
        this.socketChannel = socketChannel;
        this.byteBuffer = byteBuffer;
    }
}
总结

NIO的网络通信模型,其实就是本文标题的Reactor模式,也就是反应器模式。按照我的理解的话,Reactor是什么?Reactor是一个事件驱动的,能够同步处理多个输入源的工作模式,在Java中借助IO多路复用技术,将多个输入源的事件,交付给相应的Handler去处理。如果要跟之前的BIO进行区分的话,我们可以画出来下面这个工作流程图。

在这里插入图片描述

AIO网络通信模型

AIO是一种异步非阻塞的网络IO模型,在这里,关键词在于异步,我们可以理解到的是在调用方调用被调用方的时候,被调用方会直接返回,而在被调用的函数真正执行完成以后再通过某种方式告知调用方,就像下面这段vue代码一样。

this.$axios.post('/customer/login', {}).then((data) => {  //(1)
    this.logining = false;
    this.$router.push({path: '/default'})
}).catch((error) => { //(2)
    console.log("error");
})
  1. 一个登陆请求发起后,代码继续往后执行,如果请求成功,则执行(1)的成功回调

  2. 一个登陆请求发起后,代码继续往后执行,如果请求成功,则执行(2)的失败回调

在前端里面这个叫做回调函数,Matlab里面对于控件的操作也有回调函数,而在Object-C和swift中则称这种操作叫闭包。按照Java这种谁家有好东西我都要抄一份的习性,Java也在后来的jdk中内置了这种回调机制,也就是AIO,并且单独放到了java.aio.*包下。

那么具体怎么用呢?jdk提供了一个接口CompletionHandler,我们先来看一下结构

public interface CompletionHandler<V,A> {

    /**
     * Invoked when an operation has completed.
     *
     * @param   result
     *          The result of the I/O operation.
     * @param   attachment
     *          The object attached to the I/O operation when it was initiated.
     */
    void completed(V result, A attachment);

    /**
     * Invoked when an operation fails.
     *
     * @param   exc
     *          The exception to indicate why the I/O operation failed
     * @param   attachment
     *          The object attached to the I/O operation when it was initiated.
     */
    void failed(Throwable exc, A attachment);
}

可以看到的是,接口提供两个函数,一个是completed,也就是成功的回调,一个是failed也就是失败的回调。开发者可以通过实现接口的方式定义自己的回调逻辑,并且通过设置接口处的范型,来实现对应的处理。如此一来的话,我们就基本具备了进行类似于上面回调函数式异步开发的基础。

整体思路与代码实现

在AIO的IO模型中,主要的操作分为以下几步

  • 初始化通道

  • 初始化回调函数

  • 给通道设置回调

在AIO也使用服务通道进行客户端的连接,不过此时用的就是AsynchronousServerSocketChannel了,只需要调用服务通道的accept并设置相应的回调函数,即可完成对于客户端连接请求的处理。没错,就是这么简单,AIO的代码写出来可以说是很优雅的。

/**
 * @author mingke
 * @function AIO的实例,利用AIO的回调机制,实现的一个异步非阻塞的IO模型
 * @date 2021/11/9
 */
public class AIOSocketServer {
    //启动服务端的代码
    public void AIOServerRun() {
        //设置服务器的端口号
        InetSocketAddress inetSocketAddress = new InetSocketAddress(2021);
        try {
            final AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
            channel.bind(inetSocketAddress);
            System.out.println("服务器启动完毕");
            channel.accept(channel, new AIOAcceptHandler());
        }catch(Exception e) {
            e.printStackTrace();
        }
        //弄一个线程过来的目的是不让当前这个服务端直接结束
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }

}

回调函数的实现类如下

/**
 * @author mingke
 * @function AIO外设连接成功的回调
 * @date 2021/11/9
 */
public class AIOAcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel> {

    @Override
    public void completed(AsynchronousSocketChannel socketChannel, AsynchronousServerSocketChannel attachment) {
        try {
            //设置TCP的参数
            socketChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
            socketChannel.setOption(StandardSocketOptions.SO_SNDBUF, 1024);
            socketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024);
            //设置读取数据的回调
            if(socketChannel.isOpen()) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                byteBuffer.clear();
                socketChannel.read(byteBuffer, byteBuffer, new AIOReadHandler(socketChannel));
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //让服务器继续接受其他请求
            attachment.accept(attachment, this);
        }
    }

    @Override
    public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {
        System.out.println("监听失败了");
        try {
            exc.printStackTrace();
        }finally {
            //让服务器继续接受其他请求
            attachment.accept(attachment, this);
        }
    }
}

是否感觉到了一种赏心悦目的感觉,在这个情景下,我们设置回调函数的接口范型是服务通道本身,这样就方便在回调函数里设置服务通道继续接受其他客户端的连接请求,并且得到当前连接的客户端的Socket通道,设置read函数的回调。

/**
 * @author mingke
 * @function AIO读出信息的回调
 * @date 2021/11/9
 */
public class AIOReadHandler implements CompletionHandler<Integer, ByteBuffer> {

    private AsynchronousSocketChannel socketChannel;

    public AIOReadHandler(AsynchronousSocketChannel channel) {
        this.socketChannel = channel;
    }

    @Override
    public void completed(Integer result, ByteBuffer buffer) {
        if(result == -1) {
            System.out.println("断开连接");
        }else {
            buffer.flip();
            int bufferLength = buffer.limit();
            byte bytes[] = new byte[bufferLength];
            buffer.get(bytes, 0, bufferLength);
            System.out.println("数据长度为:"+bufferLength+" 数据内容如下");
            for(int i = 0; i < bufferLength; i++) {
                if(i == bufferLength-1) {
                    System.out.print(bytes[i] + "\n");
                }else {
                    System.out.print(bytes[i] + " ");
                }
            }
            buffer.clear();
        }
        //继续监听回调
        socketChannel.read(buffer, buffer, this);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            exc.printStackTrace();
        }finally {
            System.out.println("结束了");
        }
    }
}

在读取数据的回调函数的接口范型中,我们设置ByteBuffer作为传入的范型对象,方便后面解析数据,因为参数result只能指示读入操作是否已经完成,这里其实是一个Future类,有时间再去分析它的作用。但其实这里是远远不够的,应该在读入之后还要像接受连接那样,继续设置通道接受来自客户端的消息,否则当前的IO模型则只能接受请求发来的第一个消息,后续的消息则会因为回调的执行完毕而丢弃。所以需要在回调的类的构造器里面传入一个SocketChannel。

总结

AIO提供了一种异步非阻塞的操作手段来处理IO请求,这使得原先复杂的处理逻辑变得简洁而清晰,不过遗憾的是Redis和Netty这样的开源项目中使用的大都是NIO的网络结构,明面上我搜索到的解释都是说AIO不够成熟,可能真的是如此吧,我觉得AIO写起来更爽一点。下面是AIO的网络请求结构图。

在这里插入图片描述

NIO使用到的是Reactor模式,而人们往往说AIO用的是Acceptor模式,在高并发的情况下AIO肯定还是更胜一筹的,不过还是那句话,要根据场景来选择技术,没有最好的,只有最适合的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值