第一課 NIO

傳統IO的特點

  • 阻塞點 server.accept(); inputStream.read(bytes);
  • 單線程情況下只能有一個客戶端
  • 用線程池可以有多個客戶端連接,但是非常消耗性能
  • 無法作爲長連接服務器可以做短連接(舊版本Tomcat)

這裏寫圖片描述

NIO的關鍵詞


ServerSocketChannel ServerSocket

SocketChannel       Socket

Selector

SelectionKey

這裏寫圖片描述

NIO的一些問題

1、客戶端關閉的時候會拋出異常,死循環
解決方案
        int read = channel.read(buffer);
        if(read > 0){
            byte[] data = buffer.array();
            String msg = new String(data).trim();
            System.out.println("服務端收到信息:" + msg);

            //回寫數據
            ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
            channel.write(outBuffer);// 將消息回送給客戶端
        }else{
            System.out.println("客戶端關閉");
            key.cancel();
        }

2、selector.select();阻塞,那爲什麼說nio是非阻塞的IO?

    selector.select()
    selector.select(1000);不阻塞
    selector.wakeup();也可以喚醒selector
    selector.selectNow();也可以立馬返還,視頻裏忘了講了,哈,這裏補上


3、SelectionKey.OP_WRITE是代表什麼意思

OP_WRITE表示底層緩衝區是否有空間,是則響應返還true

NIOServer示例


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

    /**
     * 獲得一個ServerSocket通道,並對該通道做一些初始化的工作
     * 
     * @param port
     *            綁定的端口號
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        // 獲得一個ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 設置通道爲非阻塞
        serverChannel.configureBlocking(false);
        // 將該通道對應的ServerSocket綁定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 獲得一個通道管理器
        this.selector = Selector.open();
        // 將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後,
        // 當該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**
     * 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
     * 
     * @throws IOException
     */
    public void listen() throws IOException {
        System.out.println("服務端啓動成功!");
        // 輪詢訪問selector
        while (true) {
            // 當註冊的事件到達時,方法返回;否則,該方法會一直阻塞
            selector.select();
            // 獲得selector中選中的項的迭代器,選中的項爲註冊的事件
            Iterator<?> ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key,以防重複處理
                ite.remove();

                handler(key);
            }
        }
    }

    /**
     * 處理請求
     * 
     * @param key
     * @throws IOException
     */
    public void handler(SelectionKey key) throws IOException {

        // 客戶端請求連接事件
        if (key.isAcceptable()) {
            handlerAccept(key);
            // 獲得了可讀的事件
        } else if (key.isReadable()) {
            handelerRead(key);
        }
    }

    /**
     * 處理連接請求
     * 
     * @param key
     * @throws IOException
     */
    public void handlerAccept(SelectionKey key) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        // 獲得和客戶端連接的通道
        SocketChannel channel = server.accept();
        // 設置成非阻塞
        channel.configureBlocking(false);

        // 在這裏可以給客戶端發送信息哦
        System.out.println("新的客戶端連接");
        // 在和客戶端連接成功之後,爲了可以接收到客戶端的信息,需要給通道設置讀的權限。
        channel.register(this.selector, SelectionKey.OP_READ);
    }

    /**
     * 處理讀的事件
     * 
     * @param key
     * @throws IOException
     */
    public void handelerRead(SelectionKey key) throws IOException {
        // 服務器可讀取消息:得到事件發生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 創建讀取的緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int read = channel.read(buffer);
        if(read > 0){
            byte[] data = buffer.array();
            String msg = new String(data).trim();
            System.out.println("服務端收到信息:" + msg);

            //回寫數據
            ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
            channel.write(outBuffer);// 將消息回送給客戶端
        }else{
            System.out.println("客戶端關閉");
            key.cancel();
        }
    }

    /**
     * 啓動服務端測試
     * 
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8000);
        server.listen();
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章