NIO:即非阻塞式IOhtml
視頻教程: https://chuanke.baidu.com/v1982732-211322-1316084.html java
使用步驟:服務器
一、建立 ServerSocketChannel 和業務處理線程池。
二、綁定監聽端口,並配置爲非阻塞模式。
三、建立 多路複用器Selector,將以前建立的 ServerSocketChannel 註冊到 Selector 上,監聽 SelectionKey.OP_ACCEPT。
四、循環執行 Selector.select() 方法,輪詢就緒的 Channel。
五、Selector輪詢就緒的 Channel 時,若是是處於 OP_ACCEPT 狀態,說明是新的客戶端接入,調用 ServerSocketChannel.accept 接收新的客戶端。
六、設置新接入的 SocketChannel 爲非阻塞模式,並註冊到 Selector 上,監聽 OP_READ。
七、若是Selector輪詢的 Channel 狀態是 OP_READ,說明有新的就緒數據包須要讀取,則構造 ByteBuffer 對象,讀取數據。socket
先啓動服務端,不停監聽客戶端鏈接spa
服務端代碼.net
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; public class NIOServer { /*標識數字*/ private int flag = 0; /*緩衝區大小*/ private int BLOCK = 4096; /*接受數據緩衝區*/ private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); /*發送數據緩衝區*/ private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); //選擇器 private Selector selector; public NIOServer(int port) throws IOException { //1. 獲取通道, 打開服務器套接字通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //2. 切換到非阻塞模式 serverSocketChannel.configureBlocking(false); //3. 綁定鏈接的端口 serverSocketChannel.socket().bind(new InetSocketAddress(port)); //4. 獲取選擇器 selector = Selector.open(); //5. 通道註冊到選擇器上,指定監聽事件:接收。……等待鏈接 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Server Start----8888:"); } //監聽 public void listen() throws IOException { while(true) { //6. 循環獲取選擇器上已經「準備就緒」的事件,返回的int值表示有多少個通道在上一次select後發生了註冊事件 int nKeys = selector.select(); if(nKeys>0){ //7. 獲取當前選擇器中全部註冊的選擇鍵(已就緒的監聽事件) Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()){ //8. 獲取「準備就緒」的事件 SelectionKey selectionKey = iterator.next(); //9. 判斷具體是什麼事件準備就緒,開始處理請求 handleKey(selectionKey); iterator.remove(); } } else { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } // 處理請求 private void handleKey(SelectionKey selectionKey) throws IOException { // 接受請求 ServerSocketChannel server = null; SocketChannel client = null; String receiveText; String sendText; int count=0; // 此選擇鍵的通道是否已準備好接受新的套接字鏈接。 if (selectionKey.isAcceptable()) { // 返回爲之建立此選擇鍵的通道。 server = (ServerSocketChannel) selectionKey.channel(); //10. 若「接收就緒」,獲取客戶端鏈接 client = server.accept(); //11. 切換到非阻塞模式 client.configureBlocking(false); //12. 通道註冊到選擇器上,指定監聽事件:讀 就緒 client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { // 返回爲之建立此鍵的通道。 client = (SocketChannel) selectionKey.channel(); //將緩衝區清空以備下次讀取 receivebuffer.clear(); //讀取服務器發送來的數據到緩衝區中 count = client.read(receivebuffer); if (count > 0) { receiveText = new String(receivebuffer.array(),0,count); System.out.println("服務器端接受客戶端數據--:"+receiveText); client.register(selector, SelectionKey.OP_WRITE); } } else if (selectionKey.isWritable()) { //將緩衝區清空以備下次寫入 sendbuffer.clear(); // 返回爲之建立此鍵的通道。 client = (SocketChannel) selectionKey.channel(); sendText="message from server--" + flag++; //向緩衝區中輸入數據 sendbuffer.put(sendText.getBytes()); //將緩衝區各標誌復位,由於向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位 sendbuffer.flip(); //輸出到通道 client.write(sendbuffer); System.out.println("服務器端向客戶端發送數據--:"+sendText); client.register(selector, SelectionKey.OP_READ); } } public static void main(String[] args) throws IOException { int port = 8888; NIOServer server = new NIOServer(port); server.listen(); } }
心得:線程
一、第5步中,先把服務端通道註冊到選擇器上,該選擇器等待accept接收客戶端消息code
二、轉到第9步,處理請求,若是isAcceptable()=true,準備好接收客戶端了,獲取客戶端鏈接視頻
三、第12步,客戶端通道註冊到選擇器上,並指定監聽讀。而後,繼續走到listen()方法的循環體中,server
四、 此次循環中,上面已獲取客戶端鏈接,nKeys >0了,selectionKey 是監聽讀
五、又到第9步,isReadable()=true,說明是讀事件,開始操做數據,通道讀取緩衝區
客戶端代碼
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; import java.util.Set; public class NIOClient { /*標識數字*/ private static int flag = 0; /*緩衝區大小*/ private static int BLOCK = 4096; /*接受數據緩衝區*/ private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); /*發送數據緩衝區*/ private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); /*服務器端地址*/ private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("127.0.0.1", 8888); public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // 打開socket通道 SocketChannel socketChannel = SocketChannel.open(); // 設置爲非阻塞方式 socketChannel.configureBlocking(false); // 打開選擇器 Selector selector = Selector.open(); // 註冊鏈接服務端socket動做 socketChannel.register(selector, SelectionKey.OP_CONNECT); // 鏈接 socketChannel.connect(SERVER_ADDRESS); // 分配緩衝區大小內存 Set<SelectionKey> selectionKeys; Iterator<SelectionKey> iterator; SelectionKey selectionKey; SocketChannel client; String receiveText; String sendText; int count=0; while (true) { //選擇一組鍵,其相應的通道已爲 I/O 操做準備就緒。 //此方法執行處於阻塞模式的選擇操做。 selector.select(); //返回此選擇器的已選擇鍵集。 selectionKeys = selector.selectedKeys(); //System.out.println(selectionKeys.size()); iterator = selectionKeys.iterator(); while (iterator.hasNext()) { selectionKey = iterator.next(); if (selectionKey.isConnectable()) { System.out.println("client connect"); client = (SocketChannel) selectionKey.channel(); // 判斷此通道上是否正在進行鏈接操做。 // 完成套接字通道的鏈接過程。 if (client.isConnectionPending()) { client.finishConnect(); System.out.println("完成鏈接!"); sendbuffer.clear(); sendbuffer.put("Hello,Server".getBytes()); sendbuffer.flip(); client.write(sendbuffer); } client.register(selector, SelectionKey.OP_READ); } else if (selectionKey.isReadable()) { client = (SocketChannel) selectionKey.channel(); //將緩衝區清空以備下次讀取 receivebuffer.clear(); //讀取服務器發送來的數據到緩衝區中 count=client.read(receivebuffer); if(count>0){ receiveText = new String( receivebuffer.array(),0,count); System.out.println("客戶端接受服務器端數據--:"+receiveText); client.register(selector, SelectionKey.OP_WRITE); } } else if (selectionKey.isWritable()) { sendbuffer.clear(); client = (SocketChannel) selectionKey.channel(); sendText = "message from client--" + (flag++); sendbuffer.put(sendText.getBytes()); //將緩衝區各標誌復位,由於向裏面put了數據標誌被改變要想從中讀取數據發向服務器,就要復位 sendbuffer.flip(); client.write(sendbuffer); System.out.println("客戶端向服務器端發送數據--:"+sendText); client.register(selector, SelectionKey.OP_READ); } } selectionKeys.clear(); } } }
運行效果: