Java?NIO?通道概念選擇器使用示例詳解
Java NIO通道
通道相當于一個傳遞物品的管子,兩邊都可以往對面?zhèn)鬟f東西。
有哪些通道?
對應文件IO和網(wǎng)絡IO,通道也分為一個FileChannel和三個socket通道(SocketChannel、ServerSocketChannel和DatagramChannel)
基礎(chǔ)一般情況下,一個通道必然關(guān)聯(lián)著一個文件描述符或者是文件句柄。
通道可以是單向的,也可以是雙向的(讀寫)。
socket通道可以是阻塞的或非阻塞的,F(xiàn)ileChannel只支持阻塞模式。
Scatter和Gather 發(fā)散和匯聚
從字面理解,通道支持多個緩沖區(qū)同時讀寫。這樣能夠充分利用現(xiàn)代操作系統(tǒng)多核CPU功能,同時填充或排干多個緩沖區(qū)。
Scatter/Gather是一個簡單卻強大的概念,它是指在多個緩沖區(qū)上實現(xiàn)一個簡單的 I/O 操作。對于一個 write 操作而言,數(shù)據(jù)是從幾個緩沖區(qū)按順序抽?。ǚQ為 gather)并沿著通道發(fā)送的。
緩沖區(qū)本身并不需要具備這種gather 的能力(通常它們也沒有此能力)。該 gather 過程的效果就好比全部緩沖區(qū)的內(nèi)容被連結(jié)起來,并在發(fā)送數(shù)據(jù)前存放到一個大的緩沖區(qū)中。對于 read 操作而言,從
通道讀取的數(shù)據(jù)會按順序被散布(稱為 scatter)到多個緩沖區(qū),將每個緩沖區(qū)填滿直至通道中的數(shù)據(jù)或者緩沖區(qū)的最大空間被消耗完。
大多數(shù)現(xiàn)代操作系統(tǒng)都支持本地矢量 I/O(native vectored I/O)。當您在一個通道上請求一個Scatter/Gather 操作時,該請求會被翻譯為適當?shù)谋镜卣{(diào)用來直接填充或抽取緩沖區(qū)。這是一個很大
的進步,因為減少或避免了緩沖區(qū)拷貝和系統(tǒng)調(diào)用。Scatter/Gather 應該使用直接的 ByteBuffers 以從本地 I/O 獲取最大性能優(yōu)勢。
Java NIO 選擇器
從最基礎(chǔ)的層面上來看,選擇器提供了問詢通道是否就緒操作I/O的能力,選擇器可以監(jiān)控注冊在上面的多個通道,通道注冊時會返回選擇鍵(記錄通道與選擇器之間的關(guān)聯(lián)關(guān)系),選擇器管理者這些注冊的鍵、和就緒狀態(tài)鍵的集合
SelectableChannel
所有繼承SelectableChannel的通道都可以在選擇器中注冊,F(xiàn)ileChannel沒有繼承這個類,所以無法使用選擇器
選擇鍵(SelectionKey)
選擇鍵是選擇器的重點內(nèi)容,選擇器就緒的通道通過返回選擇鍵集合來通知
public abstract class SelectionKey {
public static final int OP_READ
public static final int OP_WRITE
public static final int OP_CONNECT
public static final int OP_ACCEPT
public abstract SelectableChannel channel();
public abstract Selector selector();
public abstract void cancel();
public abstract boolean isValid();
public abstract int interestOps();
public abstract void interestOps(int ops);
public abstract int readyOps();
public final boolean isReadable()
public final boolean isWritable()
public final boolean isConnectable()
public final boolean isAcceptable()
public final Object attach(Object ob)
public final Object attachment()
}選擇鍵維護了通道和選擇器之間的關(guān)聯(lián),可以通過選擇鍵獲取Channel或Selector,鍵對象表示一種特殊的關(guān)聯(lián)關(guān)系,當這種關(guān)系需要終止時,可以調(diào)用cancel()方法取消,調(diào)用這個方法時,不會立即被取消,而是將這個鍵放到被取消的集合里,當Selector下次調(diào)用select()方法時會真正被清理掉。當通道關(guān)閉時,選擇鍵會自動被取消,當選擇器關(guān)閉時,所有鍵都會被清理掉。
一個選擇器鍵包含有兩個準備好的操作集合,包括感興趣的事件集合instrest和就緒的操作集合ready,通過掩碼保存
感興趣的事件集合interestOps()
通常一個鍵的instrest注冊時就已經(jīng)確認,但是可以在注冊后通過interestOps(newOps)傳入一個新的ops來改變這個值
channel.register(this.selector, SelectionKey.OP_READ);
上面的代碼注冊的鍵interest包含read事件,可以在對通道IO異步處理時,改變這個ops來臨時取消對read事件的關(guān)注,以防止重復處理未處理完的通道
就緒的操作集合readyOps()
通過這個方法返回就緒的操作,isReadable( ),isWritable( ),isConnectable( ), 和isAcceptable( )用來判斷這些操作是否就緒,進行下一步的處理
示范選擇器的使用
下面列舉兩個示例來示范選擇器的使用
單選擇器單線程
public abstract class AbstractNioServer {
protected final static String CHARSET = "utf-8";
protected String ip;
protected Integer port;
protected Selector selector;
public AbstractNioServer(String ip, Integer port) {
this.ip = ip;
this.port = port;
}
/**
* 客戶端連接請求
*
* @param key
*/
protected abstract void accept(SelectionKey key) throws IOException;
/**
* 讀取數(shù)據(jù)
*
* @param key
*/
protected abstract void read(SelectionKey key) throws IOException;
/**
* 初始化服務器
*
* @throws IOException
*/ public void init() throws IOException {
//設(shè)置服務器地址端口
SocketAddress address = new InetSocketAddress(this.ip, this.port);
//創(chuàng)建服務端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//綁定服務器地址
serverSocketChannel.bind(address);
//設(shè)置為非阻塞模式
serverSocketChannel.configureBlocking(false);
//創(chuàng)建一個選擇器
this.selector = Selector.open();
//將服務器通道注冊到選擇器中,ServerSocketChannel只支持accept事件注冊,validOps返回16
serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
}
public void start() throws IOException {
this.init();
while (true) {
int count = this.selector.select();
if (count == 0) {
//沒有就緒的選擇鍵
continue;
}
Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
//連接請求
accept(key);
} else if (key.isReadable()) {
//修改鍵的感興趣事件,防止被 select 重復調(diào)用,處理完事件后及時恢復
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
//讀取消息
read(key);
}
iterator.remove();
}
}
}
/**
* 恢復鍵的感興趣事件
* @param key
*/
protected void resumeInterOpsRead(SelectionKey key) {
//還原key的感興趣事件
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
//喚醒selector的select事件
key.selector().wakeup();
}
}public class SingleNioServer extends AbstractNioServer {
public static void main(String[] args) {
SingleNioServer server = new SingleNioServer("127.0.0.1", 1008);
try {
server.start();
} catch (IOException e) {
e.printStackTrace();
}
}
public SingleNioServer(String ip, Integer port) {
super(ip, port);
}
@Override
protected void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
//SocketChannel支持 read、write、connect 事件注冊,validOps返回13=1+4+8
SocketChannel channel = serverChannel.accept();
if (channel == null) {
return;
}
System.out.println("新的連接請求");
channel.configureBlocking(false);
//如果是阻塞通道進行注冊,會拋出 IllegalBlockingModeException 異常
channel.register(this.selector, SelectionKey.OP_READ);
}
@Override
protected void read(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = channel.read(buffer);
buffer.flip();
if (len > 0) {
String str = Charset.forName(CHARSET).decode(buffer).toString();
System.out.println("客戶端消息:" + str);
String msg = "消息已收到";
byte[] sendData = msg.getBytes(CHARSET);
ByteBuffer sendBuffer = ByteBuffer.wrap(sendData);
channel.write(sendBuffer);
super.resumeInterOpsRead(key);
} else if (len == -1) {
System.out.println("socket client close");
key.cancel();
channel.close();
}
} catch (IOException ex) {
key.cancel();
channel.close();
}
}
}單選擇器多線程
public class MulitpleNioServer extends AbstractNioServer {
public static void main(String[] args) throws IOException {
MulitpleNioServer server = new MulitpleNioServer("127.0.0.1", 1008);
server.start();
}
/**
* 線程池
*/
private ExecutorService executorService = Executors.newFixedThreadPool(5);
public MulitpleNioServer(String ip, Integer port) {
super(ip, port);
}
@Override
protected void accept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
//SocketChannel支持 read、write、connect 事件注冊,validOps返回13=1+4+8
SocketChannel channel = serverChannel.accept();
System.out.println("新的連接請求");
channel.configureBlocking(false);
channel.register(this.selector, SelectionKey.OP_READ);
}
@Override
protected void read(SelectionKey key) throws IOException {
executorService.submit(new Runnable() {
@Override
public void run() {
readData(key);
}
});
}
private void readData(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
try {
ByteBuffer buffer = ByteBuffer.allocate(1024);
if (channel.isOpen()) {
if (channel.isConnected()) {
int len = channel.read(buffer);
buffer.flip();
if (len > 0) {
String str = Charset.forName(CHARSET).decode(buffer).toString();
System.out.println("客戶端消息:" + str);
String msg = "消息已收到";
byte[] sendData = msg.getBytes(CHARSET);
ByteBuffer sendBuffer = ByteBuffer.wrap(sendData);
channel.write(sendBuffer);
} else if (len == -1) {
System.out.println("socket client close1");
key.cancel();
channel.close();
}
super.resumeInterOpsRead(key);
}
}
} catch (IOException ex) {
System.out.println("client is close2");
key.cancel();
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}以上就是Java NIO 通道概念選擇器使用示例詳解的詳細內(nèi)容,更多關(guān)于Java NIO 通道選擇器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis配置錯誤:java.lang.ExceptionInInitializerError
這篇文章主要介紹了Mybatis配置錯誤:java.lang.ExceptionInInitializerError的相關(guān)資料,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
Spring配置文件解析之BeanDefinitionParserDelegate詳解
這篇文章主要介紹了Spring配置文件解析之BeanDefinitionParserDelegate詳解,對于Spring的配置文件的解析處理操作是在BeanDefinitionParserDelegate中進行處理操作,接下來我們簡單介紹一下BeanDefinitionParserDelegate所做的處理操作,需要的朋友可以參考下2024-02-02
從零搭建Spring Boot腳手架整合OSS作為文件服務器的詳細教程
這篇文章主要介紹了從零搭建Spring Boot腳手架整合OSS作為文件服務器的詳細教程,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08
MyBatis-Plus實現(xiàn)條件查詢的三種格式例舉詳解
本文主要介紹了MyBatis-Plus三中條件查詢格式的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08

