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