Netty是基于Java NIO封裝的網(wǎng)絡(luò)通訊框架,只有充分理解了Java NIO才能理解好Netty的底層設(shè)計(jì)。Java NIO有幾個(gè)重要的概念Channel,Buffer,Selector。NIO是基于Channel和Buffer操作的,數(shù)據(jù)只能通過Buffer寫入到Channel或者從Channel讀出數(shù)據(jù)到Buffer中。Selector可以監(jiān)聽多個(gè)通道的事件(連接打開,數(shù)據(jù)到達(dá)),這樣便可以用一個(gè)線程監(jiān)聽多個(gè)Channel的事件,從而可以用一個(gè)線程處理多個(gè)網(wǎng)絡(luò)連接。
1.1 Channel
首先來看Channel的類圖:

主要關(guān)注三個(gè)類:
SocketChannel,ServerSocketChannel以及DataGramChannel,其中SocketChannel是一個(gè)連接到TCP網(wǎng)絡(luò)套接字的通道,可由兩種方式創(chuàng)建:
- 打開一個(gè)
SocketChannel并連接到互聯(lián)網(wǎng)上的某臺服務(wù)器。 - 一個(gè)新連接到達(dá)
ServerSocketChannel時(shí),會創(chuàng)建一個(gè)SocketChannel(即由serverSocketChannel.accept()方法返回)。
ServerSocketChannel是一個(gè)可以監(jiān)聽新進(jìn)來的TCP連接的通道, 與標(biāo)準(zhǔn)IO中的ServerSocket類似。而DatagramChannel是一個(gè)能收發(fā)UDP包的通道。
1.2 Buffer
一個(gè)Buffer對象是固定數(shù)量的數(shù)據(jù)的容器。緩沖區(qū)的工作與通道緊密聯(lián)系。通道是I/O傳輸發(fā)生時(shí)通過的入口,而緩沖區(qū)是這些數(shù)據(jù)傳輸?shù)膩碓椿蚰繕?biāo)。對于離開緩沖區(qū)的傳輸,想傳遞出去的數(shù)據(jù)被置于一個(gè)緩沖區(qū),被傳送到通道。對于傳回緩沖區(qū)的傳輸,一個(gè)通道將數(shù)據(jù)復(fù)制到所提供的緩沖區(qū)中。這里我們主要關(guān)注的是ByteBuffer,因?yàn)樽止?jié)是操作系統(tǒng)與I/O設(shè)備之間或者操作系統(tǒng)與應(yīng)用進(jìn)程之間傳遞數(shù)據(jù)使用的基本數(shù)據(jù)類型,并且Java NIO中的Channel只接受ByteBuffer作為參數(shù)。
1.3 selector
Selector是Java NIO中能夠檢測多個(gè)通道,并能夠知曉通道是否為諸如讀寫時(shí)間做好準(zhǔn)備的組件。當(dāng)我們將1.1中的一個(gè)或多個(gè)SelectableChannel注冊到一個(gè)Selector對象中時(shí),一個(gè)表示通道和選擇器關(guān)系的SelectionKey會被返回。SelectionKey將記住我們關(guān)心的通道,并且會追蹤對應(yīng)的通道是否有事件已經(jīng)就緒。當(dāng)調(diào)用一個(gè)Selector.select()方法時(shí),相關(guān)的SelectionKey將會被更新,用來檢查所有被注冊到改選擇器的通道。我們可以獲取一個(gè)SelectionKey的集合,從而找到已經(jīng)就緒的通道。通過遍歷這些SelectionKey,我們可以選擇出每個(gè)從上次調(diào)用select()開始直到現(xiàn)在已經(jīng)就緒的通道。
1.4 示例
通過之前的描述,相信你已經(jīng)對Java NIO有了初步的了解,但具體的細(xì)節(jié)還很模糊,沒關(guān)系,記住我們的目標(biāo)是自頂向下分析,也就是先整體后局部,先建立對整體的認(rèn)識再不斷完善局部細(xì)節(jié)。下面將通過一個(gè)簡單的Java NIO echo Server實(shí)例從整體上理解Java NIO建立服務(wù)的過程。
public static void startServer(int port) throws IOException{
// 打開服務(wù)端ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 設(shè)置為非阻塞模式
serverChannel.configureBlocking(false);
// 綁定一個(gè)本地端口,這樣客戶端便可以通過這個(gè)端口連接到服務(wù)器
serverChannel.bind(new InetSocketAddress(port));
// 打開selector
Selector selector = Selector.open();
// 注意關(guān)心的事件是OP_ACCEPT,表示只關(guān)心接受事件即接受客戶端到服務(wù)器的連接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// select()阻塞直到注冊的某個(gè)事件就緒并會更新SelectionKey的狀態(tài)
int readyChannels = selector.select();
if (readyChannels <= 0) {
continue;
}
// 得到就緒的key集合,key中保存有就緒的事件以及對應(yīng)的Channel通道
Set<SelectionKey> SelectorKeySet = selector.selectedKeys();
Iterator<SelectionKey> iterator = SelectorKeySet.iterator();
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 遍歷選擇鍵
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
// 處理accept事件
SocketChannel socketChannel = serverChannel.accept();
socketChannel.configureBlocking(false);
// 注意此處新增關(guān)心事件OP_READ
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 處理read事件
SocketChannel socketChannel = (SocketChannel) key.channel();
buffer.clear();
socketChannel.read(buffer);
buffer.flip();
socketChannel.write(buffer);
}
iterator.remove();
}
}
}
其中內(nèi)層while循環(huán)是事件處理的關(guān)鍵,以一個(gè)客戶端連接到服務(wù)端并發(fā)送一段數(shù)據(jù)為例分析:selector最開始只關(guān)心OP_ACCEPT事件即等待客戶端連接,當(dāng)客戶端連接到服務(wù)端完成后即OP_ACCEPT就緒,程序從selector.select()方法返回并進(jìn)入內(nèi)層循環(huán)的第一個(gè)if分支處理Accept事件。在處理過程中,會將剛接受的Channel注冊到selector并關(guān)心該Channel上的OP_READ事件,完成后會清除key并回阻塞在下一輪循環(huán)的selector.select()方法上。當(dāng)客戶端發(fā)送一段數(shù)據(jù)到服務(wù)端后,OP_READ事件就緒,程序從阻塞處返回并進(jìn)入內(nèi)層while循環(huán)的第二個(gè)if分支,從而對OP_READ事件進(jìn)行處理。本例中,會將客戶端發(fā)來的數(shù)據(jù)寫回原本Channel通道,從而完成echo服務(wù)。