私は祭壇をプルダウンする方法を見てNIO

私は祭壇をプルダウンする方法を見てNIO

1.従来のブロッキングI / O

I / Oブロッキングを指すブロッキング、ソケット関数を読み取り、書き込み機能が遮断されます。

1.2モデルをプログラミングI / Oをブロック

public static void main(String[] args) {
        
        try (ServerSocket serverSocket = new ServerSocket()) {
            // 绑定端口
            serverSocket.bind(new InetSocketAddress(8081));
            while (true) {

                // 轮询established
                Socket socket = serverSocket.accept();

                new Thread(() -> {
                    try (BufferedReader buffer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                         PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true)) {
                        // 读消息
                        while (true) {
                            String body = buffer.readLine();
                            if (body == null) {
                                break;
                            }
                            log.info("receive body: {}", body);
                        }

                        // 写消息
                        printWriter.write("server receive message!");

                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }).start();
            }

        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
复制代码

、ソケット関数を受け入れるため、読み取り機能、書き込み機能は、同期ブロックで、メインスレッドがソケット関数呼び出し、接続が確立されたTCPのポーリング状況を受け入れ続けそう。

カーネルバッファにデータがない場合、ユーザプロセスにコピーされ、用意されたカーネル・バッファ・データから読み出される関数を読み、その後、スレッドは中断され、CPUを使用する権利対応する放出されます。準備ができたデータをバッファリングするカーネルと、CPUはウェイクスレッドブロックされたデータを処理し、I / O割り込み信号に応答します。

I / Oを処理する際、接続が、システムがブロックされている場合、それはシングルスレッドである場合にハングやそこで死んだ、そして必然的に、しかし、CPUが解放され、複数のスレッドを開いて、あなたはより多くの事を処理するためにCPUをさせることができます。

I / Oモデルをブロック

I / Oのブロッキングのデメリット

スレッドに大きく依存し、拡張性の欠如。Javaスレッドが512K-1Mのメモリを取る、スレッドの数があまりにも多くのJVMのメモリオーバーフローを引き起こす可能性があります。CPUのパフォーマンスに重大なドレインを切り替えるスレッドコンテキストの数が多いです。I / Oスレッドの数が多いシステムギザギザ負荷の原因となりますアクティブです。

2. NIOプログラミング

同期I / Oモデルを非ブロック

NIO、カーネルバッファのためのデータでバッファがユーザ空間ではなく、ハングスレッドにデータをコピーする際にエラーEWOULDBLOCK一般的なポーリング処理は、read関数を呼び出すことができます直接リターンのデータがない場合。

だから、非ブロッキングソケットの読み取りを参照し、書き込み機能がブロックされていないが、ユーザーはまだそれが同期され、機能を読み書きするためのプロセスをポーリングする必要があります。中に非ブロック同期 しかし、NIOを使用すると、I / Oの多重化でCPUを使用することができますので、新しいスレッドを必要としないかもしれません私たちを提供します

2.1 I / Oの多重化

Linuxシステムでは、あなたがいる限り、ソケットがキャッシュデータ、すぐにメソッドの戻りを読んでそこにあるように、スレッドを使用して複数のソケットを監視するのepoll /選択/世論調査を使用することができ、その後、あなたはこの読めるソケットを読み取ることができ、そして場合すべてのソケットバッファが空で読み、それはつまり、ブロックされます、スレッドが中断されます。

結局のepollを使って、もっとゆっくり選択しますが、SELCTを使用してLinuxを使い始めました。

2.1.1ファイルディスクリプタの利点

  1. サポートするオープンソケットディスクリプタ(FD)が唯一のオペレーティングシステムハンドルによってファイルの最大数を制限され、最大限のサポート1024を選択します。
  2. ソケットのすべてをスキャンするたびにselcet、そして唯一のアクティブなソケットをスキャンファイルディスクリプタ。
  3. ユーザ空間のコピーにカーネル空間での加速度データを用いたmmap。

2.2 NIO作業メカニズム

NIOは、実際にはイベント駆動型モデル NIOは、最も重要なことのある、マルチプレクサ(セレクタ)NIOでは、それは準備ができて、イベントを選択する機能を提供し、我々だけに必要なチャンネル(チャンネル)セレクタに登録され、セレクタは、(epollを通じ、実際のオペレーティングシステム)継続的にポーリングチャンネル上のレジストリで方法を選択します、読み取り準備のチャンネル、執筆の準備や接続がポーリングセレクタから出てくることでしょう、そして、存在する場合とSelectionKey(とSelectionKeyを返すようにチャンネルセレクタを登録し、バインドそのときに)、コレクションのチャンネルに得ることができることは準備ができていますそれ以外の場合はセレクタが選択方法でブロックされます。

セレクタselectメソッドを呼び出して、ループのためにチャネルを介してスレッドを選択する準備ができていないが、チャネルが読み込みや書き込みの準備ができているのepollイベント通知JVMスレッドを経由して、オペレーティング・システムは、readyイベントが発生しました。だから、リスナーのような方法を選択します。

多重コア目的は、その中だけでなく、一つのスレッド、複数のチャネルを動作させるための少なくともスレッドを使用することです。意思決定へのチャネルの数に基づいて作成されたスレッドの数は、それぞれが新しいスレッドを作成するために、1023個のチャンネルを登録しました。

NIOはの中核であるマルチプレクサおよびイベントモデル、我々は実際にNIOの基本原理を理解するために、これらの2つを把握することができます。TCPの深い理解とともに、それについて複雑な気持ちを学んで元NIOは、NIOは難しいことではありませんがわかりました。NIOを使用する場合は、コアがチャンネルの世代で、イベント登録セレクターに耳を傾けます。

イベントチャネルのサポートのさまざまな種類

NIOイベントモデルの模式図

2.2.1コード例

ServerReactor

@Slf4j
public class ServerReactor implements Runnable {
    private final Selector selector;
    private final ServerSocketChannel serverSocketChannel;
    private volatile boolean stop = false;

    public ServerReactor(int port, int backlog) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(port), backlog);
        serverSocket.setReuseAddress(true);
        serverSocketChannel.configureBlocking(false);
        // 将channel注册到多路复用器上,并监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public void setStop(boolean stop) {
        this.stop = stop;
    }

    @Override
    public void run() {
        try {
            // 无限的接收客户端连接
            while (!stop && !Thread.interrupted()) {
                int num = selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    // 移除key,否则会导致事件重复消费
                    it.remove();
                    try {
                        handle(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    private void handle(SelectionKey key) throws Exception {
        if (key.isValid()) {
            // 如果是ACCEPT事件,代表是一个新的连接请求
            if (key.isAcceptable()) {
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                // 相当于三次握手后,从全连接队列中获取可用的连接
                // 必须使用accept方法消费ACCEPT事件,否则将导致多路复用器死循环
                SocketChannel socketChannel = serverSocketChannel.accept();
                // 设置为非阻塞模式,当没有可用的连接时直接返回null,而不是阻塞。
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
            }

            if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = socketChannel.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    String content = new String(bytes);
                    System.out.println("recv client content: " + content);
                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                    writeBuffer.put(("服务端已收到: " + content).getBytes());
                    writeBuffer.flip();
                    socketChannel.write(writeBuffer);

                } else if (readBytes < 0) {
                    key.cancel();
                    socketChannel.close();
                }
            }

        }
    }
}
复制代码

ClientReactor

public class ClientReactor implements Runnable {
    final String host;
    final int port;
    final SocketChannel socketChannel;
    final Selector selector;
    private volatile boolean stop = false;

    public ClientReactor(String host, int port) throws IOException {
        this.socketChannel = SocketChannel.open();
        this.socketChannel.configureBlocking(false);
        Socket socket = this.socketChannel.socket();
        socket.setTcpNoDelay(true);
        this.selector = Selector.open();
        this.host = host;
        this.port = port;

    }

    @Override
    public void run() {

        try {
            // 如果通道呈阻塞模式,则立即发起连接;
            // 如果呈非阻塞模式,则不是立即发起连接,而是在随后的某个时间才发起连接。

            // 如果连接是立即建立的,说明通道是阻塞模式,当连接成功时,则此方法返回true,连接失败出现异常。
            // 如果此通道处于阻塞模式,则此方法的调用将会阻塞,直到建立连接或发生I/O错误。

            // 如果连接不是立即建立的,说明通道是非阻塞模式,则此方法返回false,
            // 并且以后必须通过调用finishConnect()方法来验证连接是否完成
            // socketChannel.isConnectionPending()判断此通道是否正在进行连接
            if (socketChannel.connect(new InetSocketAddress(host, port))) {
                socketChannel.register(selector, SelectionKey.OP_READ);
                doWrite(socketChannel);
            } else {
                socketChannel.register(selector, SelectionKey.OP_CONNECT);

            }
            while (!stop && !Thread.interrupted()) {
                int num = selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    // 移除key,否则会导致事件重复消费
                    it.remove();
                    try {
                        handle(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

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

        }


    }

    private void handle(SelectionKey key) throws IOException {

        if (key.isValid()) {

            SocketChannel socketChannel = (SocketChannel) key.channel();

            if (key.isConnectable()) {
                if (socketChannel.finishConnect()) {
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    doWrite(socketChannel);
                }
            }

            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = socketChannel.read(readBuffer);
                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    readBuffer.get(bytes);
                    System.out.println("recv server content: " + new String(bytes));
                } else if (readBytes < 0) {
                    key.cancel();
                    socketChannel.close();
                }
            }

        }
    }

    private void doWrite(SocketChannel socketChannel) {
        Scanner scanner = new Scanner(System.in);
        new Thread(() -> {
            while (scanner.hasNext()) {
                try {

                    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
                    writeBuffer.put(scanner.nextLine().getBytes());
                    writeBuffer.flip();
                    socketChannel.write(writeBuffer);
                } catch (Exception e) {

                }
            }
        }).start();
    }
}

复制代码

参考記事:

  1. これらの神話は非常に同時ゴシップ、Jingdongはアーキテクトは、それが祭壇をプルダウン方法を見ます
  2. JavaのNIOオン
  3. バークレーの共通API関数は、詳細なTCPプロトコルを使用します
  4. 「NIOソケット技術とプログラミング・ガイド」

おすすめ

転載: juejin.im/post/5dfae986518825122671c846