這塊還是挺復雜的,挺難理解,但是多練幾遍,多看看研究研究其實也就那樣,就是一個Selector輪詢的過程,這里想要雙向通信,客戶端和服務端都需要一個Selector,並一直輪詢,
直接貼代碼:
Server:服務端:
package cn.hou.socket01._03nio01; 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.Iterator; //nio 服務端 public class Server implements Runnable { //1 多路復用器 private Selector selector; //2 建立緩沖區 private ByteBuffer readBuf=ByteBuffer.allocate(1024); private ByteBuffer writeBuf=ByteBuffer.allocate(1024); //構造函數 public Server(int port){ try { //1 打開多路復用器 this.selector=Selector.open(); //2 打開服務器通道 ServerSocketChannel ssc = ServerSocketChannel.open(); //3 設置服務器通道為非阻塞方式 ssc.configureBlocking(false); //4 綁定ip ssc.bind(new InetSocketAddress(port)); //5 把服務器通道注冊到多路復用器上,只有非阻塞信道才可以注冊選擇器.並在注冊過程中指出該信道可以進行Accept操作 ssc.register(this.selector, SelectionKey.OP_ACCEPT); System.out.println("服務器已經啟動....."); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { while(true){//一直循環 try { this.selector.select();//多路復用器開始監聽 //獲取已經注冊在多了復用器上的key通道集 Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator(); //遍歷 while (keys.hasNext()) { SelectionKey key = keys.next();//獲取key //如果是有效的 if(key.isValid()){ // 如果為阻塞狀態,一般是服務端通道 if(key.isAcceptable()){ this.accept(key); } // 如果為可讀狀態,一般是客戶端通道 if(key.isReadable()){ this.read(key); } } //從容器中移除處理過的key keys.remove(); } } catch (IOException e) { e.printStackTrace(); } } } //從客戶端通道獲取數據並進行處理 private void read(SelectionKey key) { try { //1 清空緩沖區舊的數據 this.readBuf.clear(); //2 獲取之前注冊的socket通道對象 SocketChannel sc = (SocketChannel) key.channel(); //3 讀取數據 int count = sc.read(this.readBuf); //4 如果沒有數據 if(count == -1){ key.channel().close(); key.cancel(); return; } //5 有數據則進行讀取 讀取之前需要進行復位方法(把position 和limit進行復位) this.readBuf.flip(); //6 根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據 byte[] bytes = new byte[this.readBuf.remaining()]; //7 接收緩沖區數據 this.readBuf.get(bytes); //8 打印結果 String body = new String(bytes).trim(); System.out.println("服務端接受到客戶端請求的數據: " + body); //9 告訴客戶端已收到數據 writeBuf.put("你好,客戶端,我已收到數據".getBytes()); //對緩沖區進行復位 writeBuf.flip(); //寫出數據到服務端 sc.write(writeBuf); //清空緩沖區數據 writeBuf.clear(); } catch (IOException e) { e.printStackTrace(); } } //接受一個客戶端socket進行處理 private void accept(SelectionKey key) { try { //1 獲取服務通道 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //2 執行阻塞方法,當有客戶端請求時,返回客戶端通信通道 SocketChannel sc = ssc.accept(); //3 設置阻塞模式 sc.configureBlocking(false); //4 注冊到多路復用器上,並設置可讀標識 sc.register(this.selector, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { //啟動服務器 new Thread(new Server(9527)).start(); } }
Client客戶端:
package cn.hou.socket01._03nio01; 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.SocketChannel; import java.util.Iterator; //nio 客戶端 public class Client{ //客戶端信道選擇器,輪詢讀取服務端返回數據 private Selector selector; //連接信道 private SocketChannel sc; public Client(){ try { this.sc=SocketChannel.open();//打開信道 sc.connect(new InetSocketAddress("127.0.0.1",9527));////連接服務端 sc.configureBlocking(false);//設置非阻塞 selector = Selector.open();//必須打開 //將當前客戶端注冊到多路復用器上,並設置為可讀狀態 sc.register(this.selector, SelectionKey.OP_READ); //開啟線程,一直輪詢 new Thread(()->{ while(true){//一直循環 try { this.selector.select();//多路復用器開始監聽 //獲取已經注冊在多了復用器上的key通道集 Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator(); //遍歷 while (keys.hasNext()) { SelectionKey key = keys.next();//獲取key //如果是有效的 if(key.isValid()){ // 如果為可讀狀態,讀取服務端返回的數據 if(key.isReadable()){ this.read(key); } } //從容器中移除處理過的key keys.remove(); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } catch (IOException e) { e.printStackTrace(); } } //客戶端獲取服務端返回的數據 private void read(SelectionKey key) { try { //建立寫緩沖區 ByteBuffer readBuf = ByteBuffer.allocate(1024); //2 獲取之前注冊的socket通道對象 SocketChannel sc = (SocketChannel) key.channel(); //3 讀取數據 int count = sc.read(readBuf); //4 如果沒有數據 if(count == -1){ key.channel().close(); key.cancel(); return; } //5 有數據則進行讀取 讀取之前需要進行復位方法(把position 和limit進行復位) readBuf.flip(); //6 根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據 byte[] bytes = new byte[readBuf.remaining()]; //7 接收緩沖區數據 readBuf.get(bytes); //8 打印結果 String body = new String(bytes).trim(); System.out.println("客戶端已接受到服務端返回的數據: " + body); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { //建立寫緩沖區 ByteBuffer writebuf = ByteBuffer.allocate(1024); Client client = new Client(); try { while(true){ //定義一個字節數組,然后使用系統錄入功能: byte[] bytes = new byte[1024]; System.in.read(bytes); //把數據放到緩沖區中 writebuf.put(bytes); //對緩沖區進行復位 writebuf.flip(); //寫出數據到服務端 client.sc.write(writebuf); //清空緩沖區數據 writebuf.clear(); } } catch (IOException e) { e.printStackTrace(); } finally { if(client.sc != null){ try { client.sc.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
,先啟動服務端,然后再啟動客戶端:
效果如下:
Server:
Client: