歡迎大家訪問我的個人博客:L_SKH’Blog
一、首先要知道NIO的三大組件以及他們之間的關係:
Selector 、 Channel 和 Buffer 的關係圖
關係圖的說明:
1.每個channel 都會對應一個Buffer
2.Selector 對應一個線程, 一個線程對應多個channel(連接)
3.該圖反應了有三個channel 註冊到 該selector //程序
程序切換到哪個channel 是由事件決定的, Event 就是一個重要的概念
Selector 會根據不同的事件,在各個通道上切換
4.Buffer 就是一個內存塊 , 底層是有一個數組
數據的讀取寫入是通過Buffer, 這個和BIO , BIO 中要麼是輸入流,或者是輸出流, 不能雙向,但是NIO的Buffer 是可以讀也可以寫, 需要 flip 方法切換
channel 是雙向的, 可以返回底層操作系統的情況, 比如Linux , 底層的操作系統通道就是雙向的.
二、基本流程
說明:
1.當客戶端連接時,會通過ServerSocketChannel 得到 SocketChannel
2.Selector 進行監聽 select 方法, 返回有事件發生的通道的個數.
3.將socketChannel註冊到Selector上, register(Selector sel, int ops), 一個selector上可以註冊多個SocketChannel
4.註冊後返回一個 SelectionKey, 會和該Selector 關聯(集合)
5.進一步得到各個 SelectionKey (有事件發生)。在通過 SelectionKey 反向獲取 SocketChannel , 方法 channel()
6.最後可以通過 得到的 channel , 完成業務處理
三、代碼實現
3.1NIOServer
package org.skh.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
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;
/**
* @Created IntelliJ IDEA.
* @Author L_SKH
* @Date 2019/11/26 17:19
*/
/**
* 要注意這個buffer 客戶端和channel之間存在
* 服務器與通道之間也存在
*/
public class T07_NIOServer {
public static void main(String[] args) throws IOException {
//1.創建ServerSocketChannel 相當於ServerSocket
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.開啓一個selector對象
Selector selector = Selector.open();
//3.綁定端口 在服務器監聽
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//4.設置爲非阻塞
serverSocketChannel.configureBlocking(false);
//5.把serverChannel註冊到Selector 關注accept事件 客戶端連接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.循環等待客戶端連接
while (true) {
//讓selector阻塞式的監聽一秒看是否有事件發生
if (selector.select(1000) == 0) {
System.out.println("逝去的一秒內無客戶端連接....");
continue;
}
//!=0說明有事件發生 所以我們要取得selectionKey集合
//得到關注事件的集合 進而得到對應的channel
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//使用迭代器遍歷
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) { //更具相應的key的事件來進行處理
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
//連接事件 生成socketchannel 並註冊到selector
//設置其事件爲只讀 並關聯一個Buffer
SocketChannel socketChannel = serverSocketChannel.accept();
//將socketchannel設置爲非阻塞模型
socketChannel.configureBlocking(false) ;
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) {
//通過key反向獲取對應的channel
SocketChannel channel = (SocketChannel) key.channel();
//得到關聯的buffer
ByteBuffer buffer = (ByteBuffer) key.attachment();
channel.read(buffer); //服務器與客戶端之間也有buffer 所以要讀取channel到buffer
System.out.println("From 客戶端: " + new String(buffer.array(),0,buffer.position()));
}
//處理完之後就消除 避免重複處理
keyIterator.remove();
}
}
}
}
3.2NIOClient
package org.skh.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @Created IntelliJ IDEA.
* @Author L_SKH
* @Date 2019/11/26 18:49
*/
public class T08_NIOClient {
public static void main(String[] args) throws IOException {
//得到一個channel
SocketChannel socketChannel = SocketChannel.open();
//設置非阻塞
socketChannel.configureBlocking(false) ;
InetSocketAddress address = new InetSocketAddress("localhost", 6666);
if (!socketChannel.connect(address)){
//建立連接的過程需要時間 但因爲是非阻塞的所以不會停留等待
//可以去做其他事情 所以我們寫一個while循環 以避免還沒有連接就去發送數據
//同時也可以很好的看到非阻塞的效果
while (!socketChannel.finishConnect()){
System.out.println("Doing Others While Connecting!!!");
}
}
//已建立好連接 向客戶端發送數據
String msg = "Rush_SKH" ;
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
socketChannel.write(buffer) ;
System.in.read() ;
}
}
3.3Console:
逝去的一秒內無客戶端連接....
逝去的一秒內無客戶端連接....
From 客戶端: Rush_SKH
逝去的一秒內無客戶端連接....
逝去的一秒內無客戶端連接....
逝去的一秒內無客戶端連接....
逝去的一秒內無客戶端連接....