IOシリーズ(1)IOモデルの基本的な詳細説明

目次

1. BIO(ブロッキングIO)

アプリケーションシナリオ:

1.2 NIO(ノンブロッキングIO)

アプリケーションシナリオ:

総括する:

3. AIO(NIO 2.0)

4. BIO、NIO、AIOの比較:

総括する:


Javaは、BIO、NIO、AIOの3つのネットワークプログラミングIOモードをサポートしています。IOモデルとは、データの送受信に使用されるチャネルの種類を意味します。

1. BIO(ブロッキングIO)

同期ブロッキングモデル、クライアント接続は処理スレッドに対応します。最も一般的なI / OモデルはブロッキングトライアルI / Oモデルです。すべてのソケットはデフォルトでブロックされます。例として、データグラムソケットを取り上げます。

図に示すように、プロセスはrecvfromを呼び出し、そのシステムコールは、データグラムが到着してアプリケーションプロセスのバッファにコピーされるか、エラーが発生するまで戻りません。

短所:

1. IOコードの読み取り操作はブロッキング操作です。接続がデータの読み取りおよび書き込み操作を実行しない場合、スレッドがブロックしてリソースを浪費します。

2.スレッドが多すぎると、サーバースレッドが多すぎて、プレッシャーがかかりすぎます。

アプリケーションシナリオ:

BIO方式は、接続数が比較的少なく、接続数が固定されているアーキテクチャに適しています。この方式は、より多くのサーバーリソースを必要としますが、プログラムはシンプルで理解しやすいものです。

BIOコード例:  バイオパッケージのhttps://github.com/ssy-githup/io-mode

//服务端代码:
/**
 * bio模式下的服务端
 */
public class SocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接。。");
            //阻塞方法
            final Socket socket = serverSocket.accept();
            System.out.println("有客户端连接了。。");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            //handler(socket);

        }
    }
    private static void handler(Socket socket) throws IOException {
        System.out.println("当前线程ID= " + Thread.currentThread().getId());
        byte[] bytes = new byte[1024];

        System.out.println("准备read。。");
        //接收客户端的数据,阻塞方法,没有数据可读时就阻塞
        int read = socket.getInputStream().read(bytes);
        System.out.println("read完毕。。");
        if (read != -1) {
            System.out.println("接收到客户端的数据:" + new String(bytes, 0, read));
            System.out.println("当前线程ID = " + Thread.currentThread().getId());

        }
        socket.getOutputStream().write("HelloClient".getBytes());
        socket.getOutputStream().flush();
    }
}

クライアントコード:

/**
 * bio 客户端
 */
public class SocketClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 9000);
        //向服务端发送数据
        socket.getOutputStream().write("HelloServer".getBytes());
        socket.getOutputStream().flush();
        System.out.println("向服务端发送数据结束");
        byte[] bytes = new byte[1024];
        //接收服务端回传的数据
        socket.getInputStream().read(bytes);
        System.out.println("接收到服务端的数据:" + new String(bytes));
        socket.close();
    }
}

1.2 NIO(ノンブロッキングIO)

ソケットを非ブロッキングに設定するプロセスは、カーネルに通知します。要求されたすべてのI / O操作が完了するためにプロセスをスリープ状態にする必要がある場合、プロセスをスリープ状態にしないで、エラーを返します。

同期非ブロッキング、サーバー実装モードでは、1つのスレッドで複数の要求(接続)を処理でき、クライアントから送信された接続要求はマルチプレクサセレクタに登録され、マルチプレクサはIO要求が接続されるまでポーリングします。I / O多重化の最下層は、通常Linux API(select、poll、epoll)を使用して実装され、それらの違いは次のとおりです。

  選択する 投票 epoll(jdk 1.5以降)

操作方法

トラバース

トラバース

折り返し電話

低レベルの実装

アレイ

リンクリスト

ハッシュ表

IO効率

各呼び出しは線形にトラバースされ、時間計算量はO(n)です。

各呼び出しは線形にトラバースされ、時間計算量はO(n)です。

イベント通知メソッド。IOイベントの準備ができると、システムによって登録されたコールバック関数が呼び出され、時間計算量はO(1)です。

最大接続数

キャップ付き

無制限

無制限

アプリケーションシナリオ:

NIO方式は、チャットサーバー、集中砲火システム、サーバー間通信、プログラミングなど、接続数が多く、接続が比較的短い(軽い操作)アーキテクチャに適しています。JDK1.4がサポートを開始しました。

NIOには、チャネル(チャネル)、バッファー(バッファー)、セレクター(セレクター)の3つのコアコンポーネントがあります。

1.チャネルはストリームに似ており、各チャネルはバッファバッファに対応し、バッファの最下層は配列です。

2.チャネルはセレクターに登録され、セレクターはチャネルの読み取りおよび書き込みイベントの発生に応じて処理するためにアイドル状態のスレッドにそれを渡します。

3.セレクターは1つ以上のスレッドに対応できます

4.NIOのバッファとチャネルは読み取りと書き込みの両方が可能です

NIOコード例:サーバーコード

public class NIOServer {


    public static void main(String[] args) throws IOException {
        // 创建一个在本地端口进行监听的服务Socket通道.并设置为非阻塞方式
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必须配置为非阻塞才能往selector上注册,否则会报错,selector模式本身就是非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(8888));
        // 创建一个选择器selector
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            System.out.println("等待事件发生。。");
            // 轮询监听channel里的key,select是阻塞的,accept()也是阻塞的
            int select = selector.select();

            System.out.println("有事件发生了。。");
            // 有客户端请求,被轮询监听到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //删除本次已处理的key,防止下次select重复处理
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            System.out.println("有客户端连接事件发生了。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //NIO非阻塞体现:此处accept方法是阻塞的,但是这里因为是发生了连接事件,所以这个方法会马上执行完,不会阻塞
            //处理完连接请求不会继续等待客户端的数据发送
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //通过Selector监听Channel时对读事件感兴趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            System.out.println("有客户端数据可读事件发生了。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //NIO非阻塞体现:首先read方法不会阻塞,其次这种事件响应模型,当调用到read方法时肯定是发生了客户端发送数据的事件
            int len = sc.read(buffer);
            if (len != -1) {
                System.out.println("读取到客户端发送的数据:" + new String(buffer.array(), 0, len));
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        } else if (key.isWritable()) {
            SocketChannel sc = (SocketChannel) key.channel();
            System.out.println("write事件");
            // NIO事件触发是水平触发
            // 使用Java的NIO编程的时候,在没有数据可以往外写的时候要取消写事件,
            // 在有数据往外写的时候再注册写事件
            key.interestOps(SelectionKey.OP_READ);
            //sc.close();
        }
    }
}

クライアントコード:

public class NioClient {
    //通道管理器
    private Selector selector;

    /**
     * 启动客户端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NioClient client = new NioClient();
        client.initClient("127.0.0.1", 8888);
        client.connect();
    }

    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     *
     * @param ip   连接的服务器的ip
     * @param port 连接的服务器的端口号
     * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException {
        // 获得一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 获得一个通道管理器
        this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
        //用channel.finishConnect() 才能完成连接
        channel.connect(new InetSocketAddress(ip, port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    public void connect() throws IOException {
        // 轮询访问selector
        while (true) {
            selector.select();
            // 获得selector中选中的项的迭代器
            Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                // 删除已选的key,以防重复处理
                it.remove();
                // 连接事件发生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 如果正在连接,则完成连接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    //在这里可以给服务端发送信息哦
                    ByteBuffer buffer = ByteBuffer.wrap("HelloServer".getBytes());
                    channel.write(buffer);
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);                                            // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    /**
     * 处理读取服务端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        //和服务端的read方法一样
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = channel.read(buffer);
        if (len != -1) {
            System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
        }
    }
}

NIOサーバーのコードフロー:

  1. ServerSocketChannelとセレクターを作成し、ServerSocketChannelをセレクターに登録します
  2. セレクターはselect()メソッドを介してチャネルイベントを監視します。クライアントが接続すると、セレクターは接続イベントを監視し、ServerSocketChannelが登録されたときにバインドされたselectionKeyを取得します。
  3. selectionKeyは、channel()メソッドを介してバインドされたServerSocketChannelを取得できます
  4. ServerSocketChannelはaccept()メソッドを介してSocketChannelを取得します
  5. SocketChannelをセレクターに登録し、読み取りイベントに注意してください
  6. 登録後にSelectionKeyを返します。これは、SocketChannelに関連付けられます。
  7. セレクターは引き続きselect()メソッドを介してイベントを監視します。クライアントがサーバーにデータを送信すると、セレクターは読み取りイベントをリッスンし、SocketChannelが登録されたときにバインドされたselectionKeyを取得します。
  8. selectionKeyは、channel()メソッドを介してバインドされたsocketChannelを取得できます
  9. socketChannelのデータを読み取ります
  10. socketChannelを使用してサーバーデータをクライアントに書き戻します

総括する:

NIOモデルのセレクターは、さまざまなIOイベントを監視し、それらを処理のためにバックエンドスレッドに転送する、大きなマネージャーのようなものです。BIOに関連するNIOの非ブロッキングの兆候は、クライアントがデータを書き込むのを待つためにBIOのバックエンドスレッドをブロックする必要があることです(読み取りメソッドなど)。クライアントがデータを書き込まない場合、スレッドは次のようになります。セレクターは、登録されているすべてのクライアントのポーリングを担当し、イベントが発生したときに処理するためにバックエンドスレッドにのみ転送されます。バックエンドスレッドはブロッキング待機を行う必要はなく、クライアントイベントのデータを直接処理できます。終了直後に終了するか、スレッドプールに戻って他のクライアントイベントを引き続き使用します。チャネルの読み取りと書き込みもあり、ノンブロッキングです

3. AIO(NIO 2.0)

非同期でノンブロッキング。オペレーティングシステムが完了すると、サーバープログラムがコールバックされ、処理するスレッドを開始するようサーバープログラムに通知されます。これは、一般に、接続数が多く、接続時間が長いアプリケーションに適しています。

アプリケーションシナリオ:AIO方式は、接続数が多く、接続が比較的長い(重い操作)アーキテクチャに適しています。JDK7がサポートを開始しました

4. BIO、NIO、AIOの比較:

  バイオ NIO AIO
IOモデル 同期ブロッキング 同期ノンブロッキング 非同期ノンブロッキング
コーディングが簡単 シンプル 繁雑 繁雑
信頼性 すごい すごい
スループット 高い 高い

総括する:

 

 

おすすめ

転載: blog.csdn.net/qq_38130094/article/details/104721833