Java NIO中Write事件和Connect事件

NIO Server端多路複用開發的通常步驟是:
//打開選擇器
Selector selector = Selector.open();
//打開通到
ServerSocketChannel socketChannel = ServerSocketChannel.open();
//配置非阻塞模型
socketChannel.configureBlocking(false);
//綁定端口
socketChannel.bind(new InetSocketAddress(8080));
//註冊事件,OP_ACCEPT只適用於ServerSocketChannel 
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
    selector.select();
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectionKeys.iterator();
    while(iter.hasNext()) {
        SelectionKey key = iter.next();
        if(key.isAcceptable()) {
            SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
            channel.configureBlocking(false);
            channel.register(selector,SelectionKey.OP_READ);
        }
        if(key.isWritable()) {
        }
        if(key.isReadable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer readBuffer = ByteBuffer.allocate(512);
            channel.read(readBuffer);
            readBuffer.flip();
            //handler Buffer
            //通常是響應客戶端的數據
            //直接是write寫不就完事了嘛,爲啥須要write事件?
            //channel.write(...)
        }
        iter.remove();
    }
}

剛開始對NIO的寫操做理解的不深,不知道爲何要註冊寫事件,什麼時候註冊寫事件,爲何寫完以後要取消註冊寫事件。java

若是有channelSelector上註冊了SelectionKey.OP_WRITE事件,在調用selector.select();時,系統會檢查內核寫緩衝區是否可寫(何時是不可寫的呢,好比緩衝區已滿,channel調用了shutdownOutPut等等),若是可寫,selector.select();當即返回,隨後進入key.isWritable()分支。編程

固然你在channel上能夠直接調用write(...),也能夠將數據發送出去,但這樣不夠靈活,並且可能浪費CPU網絡

看一個場景,服務端須要發送一個200MBuffer,看看使用OP_WRITE事件和不使用的區別。異步

//不使用事件,缺點是,程序運行到這會等到200M文件發送完成後才繼續往下執行,不符合異步事件模型
//的編程思想,若是緩衝區一直處於不可寫狀態,那麼該過程一直在這裏死循環,浪費了CPU資源。
ByteBuffer buffer = .... //200M的Buffer
while(buffer.hasRemaining()) {
    //該方法只會寫入小於socket's output buffer空閒區域的任何字節數
    //並返回寫入的字節數,多是0字節。
    channel.write(buffer);
}

//使用事件的方式,誰好誰壞,一看便知
if(key.isReadable()) {
    ByteBuffer buffer = .... //200M的Buffer
    //註冊寫事件
    key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
    //綁定Buffer
    key.attach(buffer);
}
//isWritable分支
if(key.isWritable()) {
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    SocketChannel channel = (SocketChannel) key.channel();
    if (buffer.hasRemaining()) {
        channel.write(buffer)
    } else {
        //發送完了就取消寫事件,不然下次還會進入該分支
        key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
    }
}
客戶端開發的通常步驟:
//打開選擇器
Selector selector = Selector.open();
//打開通道
SocketChannel socketChannel = SocketChannel.open();
//配置非阻塞模型
socketChannel.configureBlocking(false);
//鏈接遠程主機
socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
//註冊事件
socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
//循環處理
while (true) {
    selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> iter = keys.iterator();
    while(iter.hasNext()) {
        SelectionKey key = iter.next();
        if(key.isConnectable()) {
            //鏈接創建或者鏈接創建不成功
            SocketChannel channel = (SocketChannel) key.channel();
            //完成鏈接的創建
            if(channel.finishConnect()) {
                
            }
        }
        if(key.isReadable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(500 * 1024 * 1024);
            buffer.clear();
            channel.read(buffer);
            //buffer Handler
        }
        iter.remove();
    }
}

起初對OP_CONNECT事件還有finishConnect不理解,OP_CONNECT事件什麼時候觸發,特別是爲何要在key.isConnectable()分支裏調用finishConnect方法後才能進行讀寫操做。socket

首先,在non-blocking模式下調用socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));鏈接遠程主機,若是鏈接能當即創建就像本地鏈接同樣,該方法會當即返回true,不然該方法會當即返回false,而後系統底層進行三次握手創建鏈接。鏈接有兩種結果,一種是成功鏈接,第二種是異常,可是connect方法已經返回,沒法經過該方法的返回值或者是異常來通知用戶程序創建鏈接的狀況,因此由OP_CONNECT事件和finishConnect方法來通知用戶程序。無論系統底層三次鏈接是否成功,selector都會被喚醒繼而觸發OP_CONNECT事件,若是握手成功,而且該鏈接未被其餘線程關閉,finishConnect會返回true,而後就能夠順利的進行channle讀寫。若是網絡故障,或者遠程主機故障,握手不成功,用戶程序能夠經過finishConnect方法得到底層的異常通知,進而處理異常。線程

相關文章
相關標籤/搜索