【Netty】NIO 选择器 ( Selector ) 通道 ( Channel ) 缓冲区 ( Buffer ) 网络通信案例
文章目錄
- I . NIO 通信 服務器端 流程說明
- II . NIO 通信 服務器端代碼
- III . NIO 通信 客戶端 流程說明
- IV . NIO 通信 客戶端代碼
- V . NIO 通信 示例運行
I . NIO 通信 服務器端 流程說明
NIO 網絡通信 服務器端 操作流程 , 與 BIO 原理類似 , 基本流程是 啟動服務器套接字通道 , 創建選擇器 , 將服務器套接字通道注冊給選擇器 , 監聽客戶端連接事件 , 客戶端連接成功后 , 創建套接字通道 , 將新創建的通道注冊給選擇器 , 然后監聽該通道的讀取事件 ;
啟動 -> 創建選擇器 ->
創建服務器通道 -> 注冊服務器通道 -> 監聽連接 ->
創建客戶端通道 -> 注冊客戶端通道 -> 監聽數據讀取/客戶端連接
1 . 創建 服務器套接字通道 ( ServerSocketChannel ) :
① 創建通道 : 調用 ServerSocketChannel.open() 創建 , 創建后需要綁定本地端口號 , 需要獲取 ServerSocket 用于綁定端口號 ;
② 獲取服務器套接字 : 可以通過服務器套接字通道的 serverSocketChannel.socket() 方法獲取 ServerSocket ;
③ 綁定本地端口號 : 調用 serverSocket.bind(new InetSocketAddress(8888)) 方法為該 ServerSocket 綁定 8888 端口號 ;
④ 設置非阻塞網絡通信模式 : 并設置該通道網絡通信模式為非阻塞模式 serverSocketChannel.configureBlocking(false) , 注意這里設置了非阻塞模式 , 其 對應的客戶端套接字通道 SocketChannel 也要設置非阻塞模式 , 否則會報 IllegalBlockingModeException 異常 ;
2 . 創建選擇器并注冊通道 :
① 創建 選擇器 ( Selector ) : 調用 Selector 的靜態方法 open() , 即可創建一個 選擇器 , Selector.open() ;
② 將 服務器套接字通道 注冊給 選擇器 ( Selector ) : serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT) , 監聽通道的 SelectionKey.OP_ACCEPT 事件 , 如果有客戶端連接服務器 , 就會觸發該事件 , 生成相應的 SelectionKey , 并放入 選擇器 ( Selector ) 的集合中 , 如果 選擇器 ( Selector ) 正在調用 select 方法 ( 333 種 阻塞 / 非阻塞 監聽方法 ) 監聽 , 那么就會解除阻塞 ( 如果之前發生監聽阻塞 ) , 返回觸發事件的個數 ;
3 . 選擇器 ( Selector ) 阻塞監聽 : 這里調用 selector.select() 方法 , 阻塞監聽 , 如果有事件發生 , 就會返回觸發事件的個數 , 之后再遍歷 SelectionKey 集合 , 依次處理觸發的事件 ; 阻塞監聽代碼邏輯如下 :
while (true){if(selector.select() <= 0){continue;}//監聽到觸發事件, 處理對應的 SelectionKey 事件 }4 . 處理客戶端連接事件 :
① 判定 SelectionKey 的事件是否是連接事件 : 調用 key.isAcceptable() 方法 , 如果返回 true , 說明該事件是客戶端連接事件 ;
② 創建 套接字通道 : 為該客戶端創建一個對應的 SocketChannel 通道 , 調用 serverSocketChannel.accept() 方法 , 可以創建該客戶端對應的 SocketChannel 通道 , 該方法是非阻塞的 , 因為該事件觸發時已經知道有客戶端連接 , 這里只是響應客戶端的連接 ;
③ 設置非阻塞網絡通信模式 : : sc.configureBlocking(false) 設置該通道是非阻塞通道 , 否則會報 IllegalBlockingModeException 異常 ;
④ 將通道注冊給選擇器 : 注冊通道給選擇器 , 并監聽數據讀取事件 , 同時設置通道對應的緩沖區 , 通道與客戶端之間使用緩沖區進行交互 ; sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)) ;
5 . 處理數據讀取事件 :
① 判定 SelectionKey 的事件是否是 讀取 事件 : 調用 key.isReadable() 方法 , 如果返回 true , 說明該事件是 數據讀取 事件 ;
② 獲取通道 : 調用 SelectionKey 的 (SocketChannel) key.channel() 方法 , 獲取該 SelectionKey 對應的通道 ;
③ 獲取緩沖區 : 調用 (ByteBuffer) key.attachment() 獲取對應的注冊給 選擇器 的緩沖區 ;
④ 讀取緩沖區的數據 : 通道 socketChannel.read(byteBuffer) 方法 , 可以將數據讀取數據到該緩沖區中 , 之后可以從緩沖區中獲取數據 ;
II . NIO 通信 服務器端代碼
服務器端代碼 :
package kim.hsl.nio.demo;import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; 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.Iterator; import java.util.Set;public class Server {public static void main(String[] args) {try {//I . 創建 ServerSocketChannel 監聽 8888 端口//創建 ServerSocketChannel, 等價于 BIO 中的 ServerSocketServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//綁定本地端口, 獲取其內部封裝的 ServerSocket, 綁定 ServerSocket 的 8888 端口ServerSocket serverSocket = serverSocketChannel.socket();serverSocket.bind(new InetSocketAddress(8888));//設置網絡通信非阻塞模式serverSocketChannel.configureBlocking(false);//II . 創建選擇器, 并注冊監聽事件//獲取 選擇器 ( Selector )Selector selector = Selector.open();//將 serverSocketChannel 通道注冊給 選擇器 ( Selector ), 這里注冊連接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//III . 選擇器監聽//選擇器 ( Selector ) 開始監聽while (true){//III . 1 . 判定事件觸發 ://阻塞監聽, 查看是否有事件觸發, 如果有就在下面處理//如果沒有 continue 終止循環, 繼續下一次循環System.out.println("服務器端開始阻塞監聽 8888 端口事件");if(selector.select() <= 0){continue;}//當前狀態說明 ://如果能執行到該位置, 說明 selector.select(1000) 方法返回值大于 0//當前有 1 個或多個事件觸發, 下面就是處理事件的邏輯//III . 2 . 處理事件集合 ://獲取當前發生的事件的 SelectionKey 集合, 通過 SelectionKey 可以獲取對應的 通道Set<SelectionKey> keys = selector.selectedKeys();//使用迭代器迭代, 涉及到刪除操作Iterator<SelectionKey> keyIterator = keys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();//根據 SelectionKey 的事件類型, 處理對應通道的業務邏輯//III . 2 . ( 1 ) 客戶端連接服務器, 服務器端需要執行 accept 操作if (key.isAcceptable()) {System.out.println("服務器端 選擇器 ( Selector ) 監聽到客戶端連接事件");//創建通道 : 為該客戶端創建一個對應的 SocketChannel 通道//不等待 : 當前已經知道有客戶端連接服務器, 因此不需要阻塞等待//非阻塞方法 : ServerSocketChannel 的 accept() 是非阻塞的方法SocketChannel sc = serverSocketChannel.accept();//如果 ServerSocketChannel 是非阻塞的, 這里的 SocketChannel 也要設置成非阻塞的//否則會報 java.nio.channels.IllegalBlockingModeException 異常sc.configureBlocking(false);//注冊通道 : 將 SocketChannel 通道注冊給 選擇器 ( Selector )//關注事件 : 關注事件時讀取事件, 服務器端從該通道讀取數據//關聯緩沖區 :sc.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));}//III . 2 . ( 2 ) 客戶端寫出數據到服務器端, 服務器端需要讀取數據if(key.isReadable()){System.out.println("服務器端 選擇器 ( Selector ) 監聽到客戶發送數據事件");//獲取 通道 ( Channel ) : 通過 SelectionKey 獲取SocketChannel socketChannel = (SocketChannel) key.channel();//獲取 緩沖區 ( Buffer ) : 獲取到 通道 ( Channel ) 關聯的 緩沖區 ( Buffer )ByteBuffer byteBuffer = (ByteBuffer) key.attachment();//讀取客戶端傳輸的數據 ;socketChannel.read(byteBuffer);System.out.println("客戶端向服務器端發送數據 : " + new String(byteBuffer.array()));}//處理完畢后, 從 Set 集合中移除該 SelectionKeykeyIterator.remove();}}} catch (IOException e) {e.printStackTrace();}} }III . NIO 通信 客戶端 流程說明
NIO 網絡通信 客戶端 操作流程 : 首先創建客戶端套接字通道 , 設置該通道為非阻塞通信模式 , 連接服務器的指定端口號 , 連接成功后 , 寫出數據到服務器中 ;
創建套接字通道 -> 連接服務器 -> 寫出數據到服務器
1 . 創建套接字通道 : 調用 SocketChannel.open() 方法 , 即可獲取套接字通道 ( SocketChannel ) , 之后將該通道設置為 非阻塞通信模式 socketChannel.configureBlocking(false) ;
2 . 連接服務器 : 首先設置服務器的地址和端口號 new InetSocketAddress(“127.0.0.1”, 8888) , 然后調用 socketChannel 的 connect(address) 方法 , 即可連接服務器 , 該操作是非阻塞的操作 , 此時需要確保連接成功以后 , 再向服務器發送數據 ;
3 . 寫出數據到服務器 : 先創建 緩沖區 Buffer , 將數據放入緩沖區 , ByteBuffer.wrap(“Hello World”.getBytes()) , 然后調用 套接字通道 ( socketChannel ) 的 write(buffer) 方法 , 將數據寫出到服務器中 ;
IV . NIO 通信 客戶端代碼
NIO 通信 客戶端代碼 :
package kim.hsl.nio.demo;import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel;public class Client {public static void main(String[] args) {try {//1 . 客戶端 SocketChannel : 先獲取 SocketChannel, 相當于 BIO 中的 Socket, 設置非阻塞模式SocketChannel socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);//服務器地址 : 服務器的 IP 地址 和 端口號InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);//2 . 連接服務器 : 連接成功, 返回 true; 連接失敗, 返回 false;boolean isConnect = socketChannel.connect(address);//沒有連接成功if (!isConnect) {while (!socketChannel.finishConnect()){System.out.println("等待連接成功");}}//當前時刻狀態分析 : 執行到該位置, 此時肯定是連接成功了System.out.println("服務器連接成功");//3 . 發送數據 : 如果連接成功 , 發送數據到服務器端ByteBuffer buffer = ByteBuffer.wrap("Hello World".getBytes());System.out.println("客戶端向服務器端發送數據 \"Hello World\"");socketChannel.write(buffer);//目的是為了阻塞客戶端, 不能讓客戶端退出System.in.read();} catch (IOException e) {e.printStackTrace();}} }V . NIO 通信 示例運行
按照以下順序操作
1 . 運行服務器端 : 服務器端運行后 , 選擇器阻塞監聽客戶端的請求 , 主要是監聽 客戶端連接 和 數據讀取 ( 服務器讀取客戶端發送的數據 ) 事件 ;
2 . 運行客戶端 : 客戶端運行后 , 連接服務器 , 然后向服務器寫出 “Hello World” 字符串數據 ;
3 . 服務器端結果 : 服務器端監聽到客戶端連接 , 為客戶端創建對應的通道 , 然后注冊監聽該通道的數據讀取事件 , 之后繼續監聽客戶端是否有數據寫入 ;
總結
以上是生活随笔為你收集整理的【Netty】NIO 选择器 ( Selector ) 通道 ( Channel ) 缓冲区 ( Buffer ) 网络通信案例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Netty】NIO 选择器 ( Sel
- 下一篇: 【Netty】NIO 网络通信 Sele