SelectKey注冊了寫事件,不在合適的時間去除掉,會一直觸發寫事件,因為寫事件是代碼觸發的
client.register(selector, SelectionKey.OP_WRITE);
或者sk.interestOps(SelectionKey.OP_WRITE)
執行了這以上任一代碼都會無限觸發寫事件,跟讀事件不同,一定注意
nio的select()的時候,只要數據通道允許寫,每次select()返回的OP_WRITE都是true。所以在nio的寫數據里面,我們在每次需要寫數據之前把數據放到緩沖區,並且注冊OP_WRITE,對selector進行wakeup(),這樣這一輪select()發現有OP_WRITE之后,將緩沖區數據寫入channel,清空緩沖區,並且反注冊OP_WRITE,寫數據完成。
這里面需要注意的是,每個SocketChannel只對應一個SelectionKey,也就是說,在上述的注冊和反注冊OP_WRITE的時候,不是通過channel.register()和key.cancel()做到的,而是通過key.interestOps()做到的。代碼如下:
public void write(MessageSession session, ByteBuffer buffer) throws ClosedChannelException {
SelectionKey key = session.key();
if((key.interestOps() & SelectionKey.OP_WRITE) == 0) {
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
}
try {
writebuf.put(buffer);
} catch(Exception e) {
System.out.println("want put:"+buffer.remaining()+", left:"+writebuf.remaining());
e.printStackTrace();
}
selector.wakeup();
}
.....
while(true) {
selector.select();
.....
if(key.isWritable()) {
MessageSession session = (MessageSession)key.attachment();
//System.out.println("Select a write");
synchronized(session) {
writebuf.flip();
SocketChannel channel = (SocketChannel)key.channel();
int count = channel.write(writebuf);
//System.out.println("write "+count+" bytes");
writebuf.clear();
key.interestOps(SelectionKey.OP_READ);
}
}
......
}
要點一:不推薦直接寫channel,而是通過緩存和attachment傳入要寫的數據,改變interestOps()來寫數據;
要點二:每個channel只對應一個SelectionKey,所以,只能改變interestOps(),不能register()和cancel()。