NIO非阻塞式I/O通信說明

以下是本人所理解的原理圖:
這裏寫圖片描述

用代碼來解釋吧:

/**
 * NIO 非阻塞時IO
 * 阻塞式 IO問題:
 * 1、客戶端過多時,要對每一個client都要創建線程 ServerSocket.accept()處理,會導致創建大量線程,每個線程都要佔用佔空間和cpu時間
 * 2、阻塞可能導致上下文切換,且大部分切換無意義。
 *
 * NIO特點:
 * 1. 由一個專門的線程來處理所有的 IO 事件,並負責分發。
 2. 事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。
 3. 線程通訊:線程之間通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的線程切換。
 *
 */

/**
 * 原理:
 * 服務端和客戶端各維護一個管理通道的對象selector,監聽各個通道上的事件
 * 服務端:
 * 如果selector註冊了讀事件,客戶端發數據過來,NIO服務端就會在selector添加一個讀事件。
 * 服務端的處理線程會輪詢訪問selector,如果訪問selector時發現有感興趣的時間到達,則處理這些事件
 * 如果沒有感興趣的事件,則處理線程會一直阻塞直到感興趣的事件到達爲止。
 *
 *
 通道上可以註冊的事件:
 key.isAcceptable()
 key.isConnectable()
 key.isReadable()
 key.isWritable()

 key.isValid()
 *
 */

/**
 * NIO服務端
 */
class NIOServer{
    //通道管理器
 private Selector selector;

    /**
 * 獲得一個ServerSocket通道,並對該通道做一些初始化工作
 * @param port
 */
 public void initServer(int port) throws IOException {

        /**
 * 1、打開一個ServerSocketChannel
 * channel是client和server對接的通道,client selector一旦接到其註冊的事件,就會推給channel;
 * server selector從channel拿到的事件如果是其之前註冊的,則推給處理線程進行處理
 * 2、設置非阻塞
 * 3、將sockect綁定到指定端口
 * 4、打開通道管理器
 * 5、管理器註冊事件:服務端接收客戶端連接事件SelectionKey.OP_ACCEPT
 */
 //1
 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2
 serverSocketChannel.configureBlocking(false);
        //3
 serverSocketChannel.bind(new InetSocketAddress(port));
        //4
 this.selector = Selector.open();
        //5
 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    }

    /**
 * 處理線程組採用輪詢的方式監聽selector是否存在需要處理的事件,有則處理
 */
 public void selectorListener() throws IOException {

        System.out.println("========== start selector ");
        //輪詢處理線程訪問selector,註冊的事件沒到達時,一直阻塞
 while(true){
            //select其實就是監控所有的處理線程
 selector.select();
            //遍歷註冊事件
 Iterator i = this.selector.selectedKeys().iterator();
            while (i.hasNext()){
                SelectionKey key = (SelectionKey) i.next();
                //刪除已選key防止重複處理
 i.remove();
                //判斷是否是客戶端請求連接事件 SelectionKey.OP_ACCEPT
 if (key.isAcceptable()){
                    //獲取服務器上當前處理線程的channel
 ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    //獲取和客戶端的連接通道
 SocketChannel channel = server.accept();
                    //設置成非阻塞式
 channel.configureBlocking(false);
                    //像客戶端發送一條消息
 channel.write(ByteBuffer.wrap(new String("send to client....").getBytes()));
                    //連接成功後爲了收到client的消息,需要給通道設置讀權限
 channel.register(this.selector,SelectionKey.OP_READ);

                }else if (key.isReadable()){
                    //此時的selector監控到的處理線程是進行處理讀客戶端的處理事件
 read(key);
                }
            }
        }
    }

    //讀取client發來的消息
 public void read(SelectionKey key) throws IOException {
         //獲取事件發生的連接通道
 SocketChannel channel = (SocketChannel) key.channel();
        //創建緩衝區
 ByteBuffer buffer = ByteBuffer.allocate(50);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("====server接收到的消息: "+msg);
        ByteBuffer out = ByteBuffer.wrap(msg.getBytes());
        //將消息回送到客戶端
 channel.write(out);

    }
    /**
 * 服務端啓動
 * @param str
 * @throws IOException
 */
 public static void main(String[] str) throws IOException {

        NIOServer server = new NIOServer();
        server.initServer(8000);
        server.selectorListener();
    }
}

class NIOClient{

    //通道管理器,監聽客戶端線程事件
 private Selector selector;

   //對selector初始化,註冊監聽事件
 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 listen() throws IOException {

        System.out.println("=======client start to listen");
        // 輪詢訪問selector
 while (true) {
            selector.select();
            // 獲得selector中選中的項的迭代器
 Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key,以防重複處理
 ite.remove();
                // 連接事件發生
 if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key
                            .channel();
                    // 如果正在連接,則完成連接
 if(channel.isConnectionPending()){
                        channel.finishConnect();

                    }
                    // 設置成非阻塞
 channel.configureBlocking(false);

                    //在這裏可以給服務端發送信息哦
 channel.write(ByteBuffer.wrap(new String("向服務端發送了一條信息").getBytes()));
                    //在和服務端連接成功之後,爲了可以接收到服務端的信息,需要給通道設置讀的權限。
 channel.register(this.selector, SelectionKey.OP_READ);

                    // 獲得了可讀的事件
 } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }
    /**
 * 處理讀取服務端發來的信息 的事件
 * @param key
 * @throws IOException
 */
 public void read(SelectionKey key) throws IOException{
        //獲取事件發生的連接通道
 SocketChannel channel = (SocketChannel) key.channel();
        //創建緩衝區
 ByteBuffer buffer = ByteBuffer.allocate(50);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("====client接收到的消息: "+msg);
    }


    /**
 * 啓動客戶端測試
 * @throws IOException
 */
 public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.initClient("localhost",8000);
        client.listen();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章