通過Netty權(quán)威指南上的一個簡單NIO時間服務(wù)器
TimeServer
import org.apache.commons.lang.StringUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
/**
* Created by liqiushi on 2017/12/8.
*/
public class TimeServer implements Runnable {
private ServerSocketChannel serverSocketChannel;
private Selector selector;
private volatile boolean stop;
public TimeServer(int port) {
try {
//1幢码、打開ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
//2睛廊、綁定監(jiān)聽端口
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("The time server start! port:" + port);
} catch (IOException e) {
e.printStackTrace();
}
//3腺劣、啟用多路復(fù)用
}
@Override
public void run() {
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey selectionKey = null;
while (iterator.hasNext()) {
selectionKey = iterator.next();
iterator.remove();
try {
handleInPut(selectionKey);
} catch (Exception e) {
if (selectionKey != null) {
selectionKey.cancel();
if (selectionKey.channel() != null) {
selectionKey.channel().close();
}
}
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void stop() {
this.stop = true;
}
private void handleInPut(SelectionKey key) throws IOException {
if (key.isValid()) {
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
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) {
//轉(zhuǎn)換成寫模式
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server recieve order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ?
new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
//write
dowrite(socketChannel, currentTime);
} else if (readBytes < 0) {
key.cancel();
socketChannel.close();
} else {
//0字節(jié)
}
}
}
}
private void dowrite(SocketChannel socketChannel, String response) throws IOException {
if (!StringUtils.isBlank(response)) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
socketChannel.write(writeBuffer);
}
}
public static void main(String[] args) {
TimeServer timeServer = new TimeServer(8001);
new Thread(timeServer, "NIO-MultiplexerTimeServer-001").start();
}
}
幾個疑問
selector
有幾個keySet:1柴底、注冊(publicKeys) 2倍试、準(zhǔn)備就緒(selectedKeys)3逗爹、調(diào)用了cancel()后谷市,將channel對應(yīng)的key加入cancelledKeys集中
cancel()
調(diào)用了SelectionKey對象的cancel方法蛔垢,這個SelectionKey對象就會被加入到cancelled-keys集合中,表示這個SelectionKey對象已經(jīng)被取消歌懒。
在每次選擇操作期間啦桌,都可以將鍵添加到選擇器的已選擇鍵集以及從中將其移除,并且可以從其鍵集和已取消鍵集中將其移除及皂。選擇是由 select()甫男、select(long) 和 selectNow() 方法執(zhí)行的,執(zhí)行涉及三個步驟:
1.將已取消鍵集中的每個鍵從所有鍵集中移除(如果該鍵是鍵集的成員)验烧,并注銷其通道板驳。此步驟使已取消鍵集成為空集。
2.在開始進行選擇操作時碍拆,應(yīng)查詢基礎(chǔ)操作系統(tǒng)來更新每個剩余通道的準(zhǔn)備就緒信息若治,以執(zhí)行由其鍵的相關(guān)集合所標(biāo)識的任意操作。對于已為至少一個這樣的操作準(zhǔn)備就緒的通道感混,執(zhí)行以下兩種操作之一:
- a.如果該通道的鍵尚未在已選擇鍵集中端幼,則將其添加到該集合中,并修改其準(zhǔn)備就緒操作集弧满,以準(zhǔn)確地標(biāo)識那些通道現(xiàn)在已報告為之準(zhǔn)備就緒的操作婆跑。丟棄準(zhǔn)備就緒操作集中以前記錄的所有準(zhǔn)備就緒信息。
- b. 如果該通道的鍵已經(jīng)在已選擇鍵集中庭呜,則修改其準(zhǔn)備就緒操作集滑进,以準(zhǔn)確地標(biāo)識所有通道已報告為之準(zhǔn)備就緒的新操作。保留準(zhǔn)備就緒操作集以前記錄的所有準(zhǔn)備就緒信息募谎;換句話說扶关,基礎(chǔ)系統(tǒng)所返回的準(zhǔn)備就緒操作集是和該鍵的當(dāng)前準(zhǔn)備就緒操作集按位分開 (bitwise-disjoined) 的。
3.如果在此步驟開始時鍵集中的所有鍵都有空的相關(guān)集合数冬,則不會更新已選擇鍵集和任意鍵的準(zhǔn)備就緒操作集节槐。
如果在步驟2的執(zhí)行過程中要將任意鍵添加到已取消鍵集中,則處理過程如步驟1。
JAVA NIO 不是同步非阻塞I/O嗎铜异,為什么說JAVA NIO提供了基于Selector的異步網(wǎng)絡(luò)I/O地来?
I/O操作 在前面的文章提到,分為兩步:
- 詢問內(nèi)核空間是否有接受到數(shù)據(jù)的到來
- 進行read/write讀寫操作
- JAVA NIO是基于select 多路復(fù)用模型熙掺,linux下(epoll)未斑,那么結(jié)論就是同步非阻塞,阻塞與非阻塞在于是否需要用戶阻塞于詢問數(shù)據(jù)的過程币绩,而同步異步在于I/O的具體操作(內(nèi)核空間到 用戶緩沖區(qū)的copy)蜡秽,數(shù)據(jù)到來,NIO依舊是一個同步操作的過程缆镣,異步則是I/O由內(nèi)核完成芽突,當(dāng)完成時再來提醒用戶進行其他的操作。
下面回答第二個問題:為什么說是異步網(wǎng)絡(luò)I/O(引用他人之言)
而說java nio提供了異步處理董瞻,這個異步應(yīng)該是指編程模型上的異步寞蚌。基于reactor模式的事件驅(qū)動钠糊,事件處理器的注冊和處理器的執(zhí)行是異步的挟秤。