BIO、NIO、AIO 讲解

1.フロンティア

コミュニケーションの枠組みでよく使われるBIO、NIO、AIOの3つのコミュニケーションモードも面接でよく聞かれますが、それらを学べば給与が変わります。3つを別々に紹介し、例を通してそれらの使用法を理解しましょう。

以下に示すように、写真を通して3つを簡単に理解しましょう。

同期ブロッキングIO: ユーザープロセスがIO操作を開始した後、実行を続行する前に、IO操作が実際に完了するのを待つ必要があります。

同期非ブロッキングIO:ユーザープロセスがIO操作を開始した後、他のことを実行できますが、ユーザープロセスはIO操作が完了したかどうかを頻繁に確認する必要があり、CPUリソースの不要な浪費を引き起こします

非同期ノンブロッキングIO:ユーザープロセスはIO操作を開始し、すぐに戻ります。IO操作が実際に完了した後、Futureモードと同様に、アプリケーションにIO操作の完了が通知されます。

、、 BIO

2.1定義

Block-IOのフルネームであるBIOは、ブロッキング同期通信モードです。これは、ソケットIOが一般的にBIOと呼ぶものです。

2.2利点

シンプルモード、使いやすい

2.3デメリット

低い並行処理能力と時間のかかる通信

2.4原則

サーバーは、Acceptorスレッドを介してクライアント要求をリッスンし、処理する要求ごとにスレッドを作成します。1対1の処理(1つの要求は1つのスレッド処理に対応します)。ここでは、頻繁な作成によるリソース損失を解決するために、スレッドの破棄、スレッドプールを使用してリクエストにスレッド処理を割り当てるように改善

2.5まとめ

BIOモデルでは、ソケットチャネルはSocketとServerSocket、ブロッキング、同期、および時間のかかる接続確立によって実現されます。

サーバーはIPアドレスとリスニングポートを提供し、クライアントはサーバーから提供されたIPアドレスとポートを使用して、TCPスリーウェイハンドシェイクを介してサーバーに接続します。接続が成功すると、ソケットソケットを介して通信します。

2.6デモ

BIOサーバーコード:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.springboot.demo.io.Constants;

/**
 * BIO服务端
 * <p>
 * BIO模型中通过Socket和ServerSocket完成套接字通道的实现。阻塞,同步,建立连接耗时
 * <p>
 * IO 也称为 BIO,Block IO 阻塞同步的通讯方式
 * 比较传统的技术,实际开发中基本上用Netty或者是AIO。熟悉BIO,NIO,体会其中变化的过程。作为一个web开发人员,stock通讯面试经常问题。
 * BIO最大的问题是:阻塞,同步。
 * BIO通讯方式很依赖于网络,若网速不好,阻塞时间会很长。每次请求都由程序执行并返回,这是同步的缺陷。
 * BIO工作流程:
 * 第一步:server端服务器启动
 * 第二步:server端服务器阻塞监听client请求
 * 第三步:server端服务器接收请求,创建线程实现任务
 *
 * @date 2019-12-03 14:18
 */
public class BIOServer {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        ThreadPoolExecutor threadPoolExecutor = null;
        try {
            // 启动服务监听
            serverSocket = new ServerSocket(Constants.BIO_PORT);
            System.out.println("BIO server starting.....");

           /* while (true){
                // 服务器监听:阻塞,等待Client请求
                socket = serverSocket.accept();
                System.out.println("server 服务器确认请求 : " + socket);
                // 服务器连接确认:确认Client请求后,创建线程执行任务  。很明显的问题,若每接收一次请求就要创建一个线程,显然是不合理的。
                new Thread(new BIOServerHandler(socket)).start();
            }*/

            threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1000,
                    TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));
            while (true) {
                // 服务器监听:阻塞,等待Client请求
                socket = serverSocket.accept();
                threadPoolExecutor.execute(new BIOServerHandler(socket));
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket != null) {
                    socket.close();
                }

                if (serverSocket != null) {
                    serverSocket.close();
                    System.out.println("BIO Server closed !!!!");
                }

                threadPoolExecutor.shutdown();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * BIO服务处理器
 *
 * @date 2019-12-03 14:25
 **/
public class BIOServerHandler implements Runnable {
    private Socket socket;

    public BIOServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader bufferedReader = null;
        PrintWriter printWriter = null;
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            printWriter = new PrintWriter(this.socket.getOutputStream(), true);
            String body = null;
            while (true) {
                // 若客户端用的是 writer.print() 传值,那readerLine() 是不能获取值的
                body = bufferedReader.readLine();
                if (body == null) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + ", BIO server receive data:" + body);
                // 返回客户端数据
                printWriter.println(body + " data is received" );
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (this.socket != null) {
                    this.socket.close();
                    this.socket = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

BIOサーバーは、主に次のロジックを実装します。

1)サービスの開始、サービスのブロック、クライアント要求のポート監視、およびクライアント要求へのスレッド処理の割り当て

2)ソケットからリクエストデータを取得し、返されたデータを書き込んで、ソケットソケットをクライアントに返します

BIOクライアントコード:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import com.springboot.demo.io.Constants;

/**
 * BIO客户端
 *
 * @date 2019-12-03 14:38
 **/
public class BIOClient {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            clientRequest(i);
        }
    }

    private static void clientRequest(int i){
        Socket socket = null;
        BufferedReader bufferedReader = null;
        PrintWriter printWriter = null;
        try {
            socket = new Socket(Constants.HOST, Constants.BIO_PORT);
            bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            printWriter = new PrintWriter(socket.getOutputStream(),true);
            String data = "客户端" + i + "数据";
            // 向服务端发送数据
            printWriter.println(data);
            System.out.println(i + " 客户端请求返回数据 : " + bufferedReader.readLine());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(socket != null){
                    socket.close();
                }
                if(bufferedReader != null){
                    bufferedReader.close();
                }
                if(printWriter != null){
                    printWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

BIOクライアントは、主に次のロジックを実装します。

1)クライアントを起動します。つまり、サーバーのIPアドレスとポートを介してサーバーとのソケット接続を確立し、要求を開始します。

2)ソケットソケットからのリクエストによって返されたデータを取得します

三、NIO

3.1定義

NIOはNewIOの略で、Non-Block IOとも呼ばれ、ノンブロッキング同期通信モードです。

3.2利点

優れた同時実行性と高スループット

3.3デメリット

モードは比較的複雑で、使用も複雑です

3.4原則

サーバーはチャネルを介してクライアントと通信し、NIOはチャネルでデータの読み取りおよび書き込み操作を実行し、これらのチャネルはすべてセレクターマルチプレクサに登録され、セレクターはスレッドを介してこれらの登録済みチャネルを継続的にポーリングし、準備されたチャネルを取り出します。 IO操作

3.5まとめ

NIOモデルでは、ソケットチャネルはSocketChannelServerSocketChannel、非ブロッキング/ブロッキング、同期によって実現され、TCP接続確立のための3方向ハンドシェイクによって引き起こされるオーバーヘッドを回避します。

NIOはスレッドを使用してチャネルをポーリングし、多くのクライアント要求を処理します

3.6用語の説明

NIOはチャネルを介して通信します。NIOのデータ操作はすべて、セレクタマルチプレクサを使用してポーリングし、バッファ内で実行されます。用語を以下に紹介します。

チャネル:ストリームとは異なり、チャネルは双方向です。NIOは、チャネルを介してデータの読み取り、書き込み、読み取り、書き込みを同時に行うことができます。チャネルは2つのカテゴリに分けられます。1つはネットワークの読み取りと書き込み用(SelectableChannel)で、もう1つはファイル操作用(FileChannel)です。使用するSocketChannelとServerSocketChannelはどちらもSelectableChannelのサブクラスです。

バッファ:これは、NIOとBIOの重要な違いです。NIOのデータ操作はすべてバッファで実行されますが、BIOはストリームストリームにあります。バッファは実際には配列であり、最も一般的なタイプのバッファはByteBufferであり、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBufferがあります。

セレクター(マルチプレクサー):NIOプログラミングの基礎であるマルチプレクサーは、準備ができているタスクを選択する機能を提供します。つまり、セレクターはそれに登録されているチャネル(チャネル)を継続的にポーリングします。チャネルが準備完了状態の場合、セレクターによってポーリングされ、選択キーを介してチャネルの準備完了セットを取得して、後続の実行を実行できます。 IO操作。サーバーがセレクターのポーリングを担当するスレッドを提供している限り、サーバーは何千ものクライアントにアクセスできます。これは、JDKNIOライブラリーの大幅な改善です。

3.7デモ

NIOサーバーコード:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;

import com.springboot.demo.io.Constants;

/**
 * NIO服务
 * <p>
 * NIO模型中通过SocketChannel和ServerSocketChannel完成套接字通道的实现。非阻塞/阻塞,同步,避免TCP建立连接使用三次握手带来的开销
 * <p>
 * NIO 也称 New IO, Non-Block IO,非阻塞同步通信方式
 * 从BIO的阻塞到NIO的非阻塞,这是一大进步。功归于Buffer,Channel,Selector三个设计实现。
 * Buffer   :  缓冲区。NIO的数据操作都是在缓冲区中进行。缓冲区实际上是一个数组。而BIO是将数据直接写入或读取到Stream对象。
 * Channel  :  通道。NIO可以通过Channel进行数据的读,写和同时读写操作。
 * Selector :  多路复用器。NIO编程的基础。多路复用器提供选择已经就绪状态任务的能力。
 * 客户端和服务器通过Channel连接,而这些Channel都要注册在Selector。Selector通过一个线程不停的轮询这些Channel。找出已经准备就绪的Channel执行IO操作。
 * NIO通过一个线程轮询,实现千万个客户端的请求,这就是非阻塞NIO的特点。
 *
 * @date 2019-12-03 15:11
 **/
public class NIOServer {

    public NIOServer() {
    }

    private Selector startNIOServer() {
        try {
            // 1、打开多路复用器
            Selector selector = Selector.open();
            // 2、打开服务器通道(网络读写通道)
            ServerSocketChannel socketChannel = ServerSocketChannel.open();
            // 3、设置服务器通道为非阻塞模式,true 为阻塞,false 为非阻塞
            socketChannel.configureBlocking(false);
            // 4、绑定端口
            socketChannel.bind(new InetSocketAddress(Constants.NIO_PORT));
            // 5、把通道注册到多路复用器上,并监听阻塞事件
            /**
             * SelectionKey.OP_READ     : 表示关注读数据就绪事件
             * SelectionKey.OP_WRITE     : 表示关注写数据就绪事件
             * SelectionKey.OP_CONNECT: 表示关注socket channel的连接完成事件
             * SelectionKey.OP_ACCEPT : 表示关注server-socket channel的accept事件
             */
            socketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NIO server starting......");
            return selector;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        Selector selector = new NIOServer().startNIOServer();
        new Thread(new NIOServerHandler(selector)).start();
    }
}



import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import com.springboot.demo.io.Constants;

/**
 * NIO服务处理器
 *
 * @date 2019-12-03 15:11
 **/
public class NIOServerHandler implements Runnable {
    // 多路复用器,NIO编程的基础,负责管理通道Channel
    private Selector selector;
    // 缓冲区buffer
    private ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);

    public NIOServerHandler(Selector selector) {
        this.selector = selector;
    }


    /**
     * 开启线程负责Selector轮询
     */
    @Override
    public void run() {
        while (true) {
            try {
                /**
                 * a.select() 阻塞到至少有一个通道在你注册的事件上就绪
                 * b.select(long timeOut) 阻塞到至少有一个通道在你注册的事件上就绪或者超时timeOut
                 * c.selectNow() 立即返回。如果没有就绪的通道则返回0
                 * select方法的返回值表示就绪通道的个数。
                 */
                // 1、多路复用器监听阻塞
                selector.select();
                System.out.println("阻塞了吗?");
                // 2、获取多路复用器已经选择的结果集
                Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
                // 3、不停地轮询所有的Channel
                while (selectionKeys.hasNext()) {
                    // 4、获取当前选中的key
                    SelectionKey key = selectionKeys.next();
                    // 5、获取后便将其从容器中移除
                    selectionKeys.remove();
                    if (!key.isValid()) {
                        // 只获取有效地key
                        continue;
                    }
                    // 新来的请求
                    if (key.isAcceptable()) {
                        accept(key);
                    }

                    // 可读状态处理
                    if (key.isReadable()) {
                        System.out.println("开始读取数据");
                        read(key);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void read(SelectionKey key) {
        try {
            // 1、清空缓冲区数据
            byteBuffer.clear();
            // 2、获取在 selector 上注册的channel
            SocketChannel socketChannel = (SocketChannel) key.channel();
            // 3、读取数据
            int index = socketChannel.read(byteBuffer);
            if (index == -1) {
                // -1 标示无任何数据
                socketChannel.close();
                key.cancel();
                return;
            }

            // 4、有数据则在读取数据前进行复位操作
            byteBuffer.flip();
            // 5、根据缓冲区大小创建一个相应大小的bytes数组,用来获取值
            byte[] bytes = new byte[byteBuffer.remaining()];
            // 6、接收缓冲区数据
            byteBuffer.get(bytes);
            String requestMsg = new String(bytes);
            System.out.println("NIO server received data:" + requestMsg);

            // 返回响应数据给客户端
            write(socketChannel, requestMsg + " is response");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回数据给客户端
     *
     * @param socketChannel
     * @param s
     */
    private void write(SocketChannel socketChannel, String s) {
        try {
            // 创建ByteBuffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
            // 将返回数据放入缓存区
            byteBuffer.put(s.getBytes());
            // 缓存区数据复位
            byteBuffer.flip();
            // 发送缓冲区数据
            socketChannel.write(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 设置阻塞,等待Client请求。在传统IO编程中,用的是ServerSocket和Socket。在NIO中采用的ServerSocketChannel和SocketChannel
    private void accept(SelectionKey key) {
        try {
            // 1、获取通道服务
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            // 2、执行阻塞方法
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 3、设置通道服务为非阻塞,true 为阻塞,false 为非阻塞
            socketChannel.configureBlocking(false);
            // 通道注册到 selector 上去,并设置读取标识
            socketChannel.register(selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

NIOサーバーは、主に次のロジックを実装します。

1)マルチプレクサセレクタを開き、チャネルServerSocketChannel、チャネルバインディングポートを開き、チャネルを登録します

2)セレクターはチャネルをポーリングし、要求イベントを処理して、応答データを返します

NIOクライアントコード:

import java.util.Scanner;

/**
 * NIO客户端
 *
 * @date 2019-12-03 16:10
 **/
public class NIOClient {

    private static NIOClientHandler nioClientHandler;

    public static void startClient(){
        nioClientHandler = new NIOClientHandler();
        new Thread(nioClientHandler).start();
    }


    public static void main(String[] args) {
        // 开启客户端
        NIOClient.startClient();

        // 发送消息
        while(nioClientHandler.sendMsg(new Scanner(System.in).nextLine()));
    }
}


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import com.springboot.demo.io.Constants;

/**
 * NIO客户端处理器
 *
 * @date 2019-12-03 15:11
 **/
public class NIOClientHandler implements Runnable {
    // 多路复用器,NIO编程的基础,负责管理通道Channel
    private Selector selector;
    private SocketChannel socketChannel;
    // 缓冲区buffer
    private ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);

    public NIOClientHandler() {
        try {
            // 1、打开多路复用器
            selector = Selector.open();
            // 2、打开 SocketChannel 通道
            socketChannel = SocketChannel.open();
            // 3、设置为非阻塞模式,true 为阻塞,false 为非阻塞
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 开启线程负责Selector轮询
     */
    @Override
    public void run() {
        doConnect();
        while (true) {
            try {
                /**
                 * a.select() 阻塞到至少有一个通道在你注册的事件上就绪
                 * b.select(long timeOut) 阻塞到至少有一个通道在你注册的事件上就绪或者超时timeOut
                 * c.selectNow() 立即返回。如果没有就绪的通道则返回0
                 * select方法的返回值表示就绪通道的个数。
                 */
                // 1、多路复用器监听阻塞
                selector.select();
                // 2、获取多路复用器已经选择的结果集
                Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
                // 3、不停地轮询所有的Channel
                while (selectionKeys.hasNext()) {
                    // 4、获取当前选中的key
                    SelectionKey key = selectionKeys.next();
                    // 5、获取后便将其从容器中移除
                    selectionKeys.remove();
                    dealEvent(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void dealEvent(SelectionKey key) {
        // 只获取有效地key
        if (key.isValid()) {
            // 连接事件
            if (key.isConnectable()) {
                System.out.println("连接事件来了");
                connect(key);
            }

            // 可读状态处理
            if (key.isReadable()) {
                System.out.println("开始读取数据");
                read(key);
            }
        }
    }

    private void connect(SelectionKey key) {
        try {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            if (!socketChannel.finishConnect()) {
                System.out.println("finish connect?");
                System.exit(1);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void doConnect() {
        try {
            System.out.println("连接了。。。。");
            InetSocketAddress socketAddress = new InetSocketAddress(Constants.HOST,Constants.NIO_PORT);
            if(!socketChannel.connect(socketAddress)){
                System.out.println("connect is false?");
                socketChannel.register(selector, SelectionKey.OP_CONNECT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void read(SelectionKey key) {
        try {
            // 1、清空缓冲区数据
            byteBuffer.clear();
            // 2、获取在 selector 上注册的channel
            SocketChannel socketChannel = (SocketChannel) key.channel();
            // 3、读取数据
            int index = socketChannel.read(byteBuffer);
            if (index == -1) {
                // -1 标示无任何数据
                socketChannel.close();
                key.cancel();
                return;
            }

            // 4、有数据则在读取数据前进行复位操作
            byteBuffer.flip();
            // 5、根据缓冲区大小创建一个相应大小的bytes数组,用来获取值
            byte[] bytes = new byte[byteBuffer.remaining()];
            // 6、接收缓冲区数据
            byteBuffer.get(bytes);
            String requestMsg = new String(bytes);
            System.out.println("NIO client received data:" + requestMsg);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回数据给客户端
     *
     * @param content
     */
    private void write(String content) {
        try {
            // 创建ByteBuffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
            // 将返回数据放入缓存区
            byteBuffer.put(content.getBytes());
            // 缓存区数据复位
            byteBuffer.flip();
            // 发送缓冲区数据
            socketChannel.write(byteBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean sendMsg(String content){
        if("stop".equals(content)){
            return false;
        }

        try {
            socketChannel.register(selector, SelectionKey.OP_READ);
            write(content);
        } catch (ClosedChannelException e) {
            e.printStackTrace();
        }

        return true;
    }
}

NIOクライアントは、主に次のロジックを実装します。

1)マルチプレクサセレクタを開き、チャネルSocketChannelを開き、チャネルを登録し、サーバーに接続します

2)、リクエストの送信、データの送信

3)、セレクターポーリングチャネル、要求応答データイベントの処理

四、AIO

4.1定義

AIOはNIO2.0とも呼ばれ、ノンブロッキング非同期通信モードです。NIOに基づいて、新しい非同期チャネルの概念が導入され、非同期ファイルチャネルと非同期ソケットチャネルの実装が提供されます。

4.2利点

NIOと比較して、同時実行性とスループットが優れています

4.3デメリット

モードは比較的複雑で、使用も複雑です

4.4原則

AIOはNIOのマルチプレクサセレクタを使用しませんが、非同期チャネルを使用します。その読み取りメソッドと書き込みメソッドの戻り値の型はFutureオブジェクトですFutureモデルは非同期であり、その中心的な考え方は次のとおりです。メイン関数でデータが返されるのを待つ

4.5まとめ

AIOモデルでは、ソケットチャネルはAsynchronousSocketChannelおよびAsynchronousServerSocketChannel、ノンブロッキング、非同期を介して実現さます。

4.6デモ

AIOサーバーコード:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.springboot.demo.io.Constants;

/**
 * AIO服务
 * <p>
 * AIO 也叫NIO2.0 是一种非阻塞异步的通信模式。在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
 * AIO 并没有采用NIO的多路复用器,而是使用异步通道的概念。其read,write方法的返回类型都是Future对象,而Future模型是异步的,
 * 其核心思想是:去主函数等待时间
 * <p>
 * 小结:AIO模型中通过AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的实现。非阻塞,异步。
 *
 * @date 2019-12-03 17:03
 **/
public class AIOServer {
    // 线程池
    private ExecutorService executorService;
    // 通道组
    private AsynchronousChannelGroup channelGroup;
    // 服务器通道
    public AsynchronousServerSocketChannel serverSocketChannel;

    public AIOServer() {
    }

    public void startAIOServer() {
        try {
            // 1、创建一个缓存线程池
            executorService = Executors.newCachedThreadPool();
            // 2、创建通道组
            channelGroup = AsynchronousChannelGroup.withCachedThreadPool(executorService, 1);
            // 3、创建服务器通道
            serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
            // 4、绑定网络
            serverSocketChannel.bind(new InetSocketAddress(Constants.AIO_PORT));
            System.out.println("AIO server starting......");
            // 等待客户端请求
            serverSocketChannel.accept(this, new AIOServerHandler());
            // 一直阻塞 不让服务器停止,真实环境是在tomcat下运行,所以不需要这行代码
            TimeUnit.MILLISECONDS.sleep(Integer.MAX_VALUE);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new AIOServer().startAIOServer();
    }
}



import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;

import com.springboot.demo.io.Constants;

/**
 * AIO服务处理器
 *
 * @date 2019-12-03 17:17
 **/
public class AIOServerHandler implements CompletionHandler<AsynchronousSocketChannel,AIOServer> {
    @Override
    public void completed(AsynchronousSocketChannel result, AIOServer attachment) {
        // 保证多个客户端都可以阻塞
        attachment.serverSocketChannel.accept(attachment,this);
        read(result);
    }

    // 读取数据
    private void read(final AsynchronousSocketChannel socketChannel) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
        socketChannel.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                // 读取之后重置标记位
                attachment.flip();
                // 获取读取的数据
                String resultData = new String(attachment.array()).trim();
                System.out.println("AIO server received data:" + resultData);
                //
                String responseContent = resultData + " response";
                write(socketChannel, responseContent);
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                exc.printStackTrace();
            }
        });
    }

    private void write(AsynchronousSocketChannel socketChannel, String responseContent) {
        try {
            ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
            // 数据放入缓冲区中
            byteBuffer.put(responseContent.getBytes());
            byteBuffer.flip();
            // 缓冲区中数据写入到通道中
            socketChannel.write(byteBuffer).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void failed(Throwable exc, AIOServer attachment) {
        exc.printStackTrace();
    }
}

AIOサーバーは、主に次のロジックを実装します。

1)サーバーチャネルAsynchronousServerSocketChannelを作成し、ポートをバインドして、クライアント要求を待ちます

2)、要求データの読み取り、データの処理、戻りデータの書き込み

AIOクライアントコード:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;

import com.springboot.demo.io.Constants;

/**
 * AIO客户端
 *
 * @date 2019-12-03 17:32
 **/
public class AIOClient implements Runnable {
    private AsynchronousSocketChannel socketChannel;

    private Integer index;

    public AIOClient(Integer index) {
        try {
            this.index = index;
            // 打开通道
            socketChannel = AsynchronousSocketChannel.open();
            // 创建连接
            socketChannel.connect(new InetSocketAddress(Constants.HOST,Constants.AIO_PORT));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        String content = index + " -> 客户端数据";
        try {
            socketChannel.write(ByteBuffer.wrap(content.getBytes())).get();
            ByteBuffer byteBuffer = ByteBuffer.allocate(Constants.BUFFER_SIZE);
            while (true){
                int count = socketChannel.read(byteBuffer).get();
                byteBuffer.flip();
                if(count == -1){
                    continue;
                }
                byte[] responseBytes = new byte[byteBuffer.remaining()];
                // 将缓冲区的数据放入到 byte数组中
                byteBuffer.get(responseBytes);
                System.out.println(index+" 客户端 receive response :" + new String(responseBytes).trim());
                break;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            AIOClient aioClient = new AIOClient(i);
            new Thread(aioClient,"t-"+i).start();
        }
    }
}

AIOクライアントは、主に次のロジックを実装します。

1)、チャネルAsynchronousSocketChannelを開き、サーバーに接続します

2)リクエストを送信し、リクエストレスポンスを読み取り、データを返します

5、BIO、NIO、AIOの違い

BIO:同期通信モードをブロックするIO、クライアントとサーバーの接続には3つのハンドシェイクが必要で、使いやすく、スループットは小さい

NIO:ノンブロッキング同期通信モード。クライアントとサーバーはチャネルを介して接続され、マルチプレクサを使用して登録済みチャネルをポーリングし、スループットと信頼性を向上させます。

AIO:NIOのアップグレードバージョンであるノンブロッキング非同期通信モードは、非同期チャネルを使用して非同期通信を実現し、その読み取りおよび書き込みメソッドは非同期メソッドです。

3つの比較は、次の表に要約されています。

 

バイオ

NIO AIO
ブロックするかどうか ブロック ノンブロッキング ノンブロッキング
同期非同期 同期する 同期する 非同期
パフォーマンス より高い 高い
スレッド比率(リクエスト:スレッド) 1:1 N:1 N:0
スループット 高い より高い

6、まとめ

BIOモデルでは、ソケットチャネルはSocketとServerSocketを介して実現されます。ブロッキング、同期、時間のかかる接続

NIOモデルでは、ソケットチャネルはSocketChannelとServerSocketChannelを介して実現されます。TCP接続確立のためのスリーウェイハンドシェイクによって引き起こされるオーバーヘッドを回避するための非ブロッキング/ブロッキング、同期

AIOモデルでは、ソケットチャネルはAsynchronousSocketChannelおよびAsynchronousServerSocketChannelを介して実現されます。ノンブロッキング、非同期

参照:https//segmentfault.com/a/1190000012976683

おすすめ

転載: blog.csdn.net/ywlmsm1224811/article/details/103400163