本文轉載自微信公眾號「yes的練級攻略」,作者是Yes呀 。轉載本文請聯系yes的練級攻略公眾號。
你好,我是yes。
在深入 Netty 之前,我覺得有必要先對齊一下 Java NIO 的基礎知識,因為 Netty 對底層網絡 I/O 的操作就是基于 Java NIO 的,所以有必要了解一下。
到時候看源碼,會有很多概念,例如 Channel、Selector、SelectionKey、Buffer 等等,這篇我們就來了解下這些名詞到底代表著什么,分別是什么意思。
關于 Java NIO 相關的核心,總的來看包含以下三點,分別是:
- Channel
- Buffer
- Selector
什么是 Channel
翻譯過來就是通道。
我們可以往通道里寫數據,也可以從通道里讀數據,它是雙向的,而與之配套的是 Buffer,也就是你想要往一個通道里寫數據,必須要將數據寫到一個 Buffer 中,然后寫到通道里。
從通道里讀數據,必須將通道的數據先讀取到一個 Buffer 中,然后再操作。
在 NIO 中 Channel 有多種類型:
- SocketChannel
- ServerSocketChannel
- DatagramChannel
- FileChannel
- SocketChannel
對標 Socket,我們可以直接將它當做所建立的連接。
通過 SocketChannel ,我們可以利用 TCP 協議進行讀寫網絡數據。
ServerSocketChannel
可以對標 ServerSocket,也就是服務端創建的 Socket。
它的作用就是監聽新建連的 TCP 連接,為新進一個連接創建對應的 SocketChannel。
之后,通過新建的 SocketChannel 就可以進行網絡數據的讀寫,與對端交互。
可以看到它主要是用來接待新連接,這功能主要就是服務端做的,所以叫 ServerSocketChannel。
DatagramChannel
看到 Datagram 應該就知道是 UDP 協議了,是無連接協議。
利用 DatagramChannel 可以直接通過 UDP 進行網絡數據的讀寫。
FileChannel
文件通道,用來進行文件的數據讀寫。
我們日常開發主要是基于 TCP 協議,所以我們把精力放在 SocketChannel 和 ServerSocketChannel 上即可。
我們再回過頭來繼續看看 SocketChannel 和 ServerSocketChannel。
SocketChannel 主要在兩個地方出現:
- 客戶端,客戶端創建一個 SocketChannel 用于連接至遠程的服務端。
- 服務端,服務端利用 ServerSocketChannel 接收新連接之后,為其創建一個 SocketChannel 。
隨后,客戶端和服務端就可以通過這兩個 SocketChannel 相互發送和接收數據。
ServerSocketChannel 主要出現在一個地方:服務端。
服務端需要綁定一個端口,然后監聽新連接的到來,這個活兒就由 ServerSocketChannel 來干。
服務端內常常會利用一個線程,一個死循環,不斷地接收新連接的到來。
- ServerSocketChannel serverSocketChannel
- = ServerSocketChannel.open();
- ......
- while(true){
- // 接收的新連接
- SocketChannel socketChannel =
- serverSocketChannel.accept();
- .......
- }
至此,想必你應該清楚 ServerSocketChannel 和 SocketChannel 的區別和作用了。
Buffer
Buffer 說白了就是內存中可以讀寫的一塊地方,叫緩沖區,用于緩存數據。
其實還真沒啥好說的,最多就講講 Java NIO Buffer 的 API。
但講 API 的太死板了,所以自己上網搜搜吧。我就告知一個結論,這個 API 很不好用,稍微漏寫了點,就容易出 bug,而且還有很多優化的之處,所以 Netty 沒用 Java NIO Buffer 而是自己實現了一個 Buffer,叫 ByteBuf。
等我們之后分析 ByteBuf 的時候再來盤一盤?,F在你只需要知道 Buffer 主要用來緩存通道的讀寫數據即可。
對了,看到這可能會有人提出疑問,為什么 Channel 必須和 Buffer 搭配使用?
其實網絡數據是面向字節的,但是我們讀寫的數據往往是多字節的,假設不用 Buffer ,那我們就得一個字節一個字節的調用讀和調用寫,想想是不是很麻煩?
所以我們搞個 Buffer,把數據攏一攏,這樣之后的調用才能更好地處理完整的數據,方便異步的處理等等。
Selector
I/O多路復用的核心玩意。
一個 Selector 上可以注冊多個 Channel ,我們從上面得知一個 Channel 就對應了一個連接,因此一個 Selector 可以管理多個 Channel 。
具體管理什么?
當任意 Channel 發生讀寫事件的時候,通過 Selector.select() 就可以捕捉到事件的發生,因此我們利用一個線程,死循環的調用 Selector.select(),這樣可以利用一個線程管理多個連接,減少了線程數,減少了線程的上下文切換和節省了線程資源。
這就是 Selector 的核心功能,然后我們再來細說具體是怎樣管理的。
首先,創建一個 Selector。
- Selector selector = Selector.open();
然后,你需要將被管理的 Channel 注冊到 Selector 上,并聲明感興趣的事件。
- SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
事件一共有以上四種類型,注冊的時候可以同時對多種類型的事件感興趣,例如:
- SelectionKey key
- = channel.register(selector,
- Selectionkey.OP_READ | SelectionKey.OP_WRITE);
這樣,當這個 Channel 發生讀或寫事件,我們調用 Selector.select() 就可以得知有事件發生。
具體 Selector.select() 有三個重載方法:
- int selectNow(),不論是否有無事件發生,立即返回
- int select(long timeout),至多阻塞 timeout 時間(或被喚醒),如果提早有事件發生,提早返回
- int select(),一直阻塞著,直到有事件發生(或被喚醒)
返回值就是就緒的通道數,一般判斷大于 0 即可進行后續的操作。
后續的操作就是調用:
- Set selectedKeys = selector.selectedKeys();
獲得了一個類型為 Set 的 selectedKeys 集合,那這個 selectedKeys 又是啥玩意?
我們來看一下它的方法和成員:
看到這些成員,其實我們就很清晰了,我們可以通過 selectedKey 得知當前發生的是什么事件,有 isAcceptable、isReadable 等等。
然后還能獲得對應的 channel 進行相應的讀寫操作,還有獲取 attachment 等等。
所以得到了 selectedKeys 就可以通過迭代器遍歷所有發生事件的連接,然后進行操作。
大致使用的代碼如下所示:
- while(true) {
- int readyNum = selector.select();
- if (readyNum == 0) {
- continue;
- }
- Set selectedKeys = selector.selectedKeys();
- Iterator keyIterator = selectedKeys.iterator();
- while(keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
- if(key.isAcceptable()) {
- // a connection was accepted by a ServerSocketChannel.
- } else if (key.isConnectable()) {
- // a connection was established with a remote server.
- } else if (key.isReadable()) {
- // a channel is ready for reading
- } else if (key.isWritable()) {
- // a channel is ready for writing
- }
- keyIterator.remove(); //執行完畢之后,需要在循環內移除自己
- }
- }
還有個方法就是 Selector.wakeup(),可以喚醒阻塞著的 Selector。
對了還有一點沒說,就是如果 Channel 要和 Selector 搭配,那它必須得是非阻塞的,即配置
- channel.configureBlocking(false);
從上面的操作,我們可以得知 Selector 處理事件的時候必須快,如果長時間處理某個事件,那么注冊到 Selector 上的其他連接的事件就不會被及時處理,造成客戶端阻塞。
至此,想必你應該清晰 Selector 具體是如何管理這么多連接的了。
參考:https://ifeve.com/java-nio-all/
原文鏈接:https://mp.weixin.qq.com/s/0p3hId2GCy48yyGH55A5Qg