Java?NIO?通道概念選擇器使用示例詳解
Java NIO通道
通道相當(dāng)于一個傳遞物品的管子,兩邊都可以往對面?zhèn)鬟f東西。
有哪些通道?
對應(yīng)文件IO和網(wǎng)絡(luò)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是一個簡單卻強(qiáng)大的概念,它是指在多個緩沖區(qū)上實(shí)現(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)。當(dāng)您在一個通道上請求一個Scatter/Gather 操作時,該請求會被翻譯為適當(dāng)?shù)谋镜卣{(diào)用來直接填充或抽取緩沖區(qū)。這是一個很大
的進(jìn)步,因?yàn)闇p少或避免了緩沖區(qū)拷貝和系統(tǒng)調(diào)用。Scatter/Gather 應(yīng)該使用直接的 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)
選擇鍵是選擇器的重點(diǎn)內(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() }
選擇鍵維護(hù)了通道和選擇器之間的關(guān)聯(lián),可以通過選擇鍵獲取Channel或Selector,鍵對象表示一種特殊的關(guān)聯(lián)關(guān)系,當(dāng)這種關(guān)系需要終止時,可以調(diào)用cancel()方法取消,調(diào)用這個方法時,不會立即被取消,而是將這個鍵放到被取消的集合里,當(dāng)Selector下次調(diào)用select()方法時會真正被清理掉。當(dāng)通道關(guān)閉時,選擇鍵會自動被取消,當(dāng)選擇器關(guān)閉時,所有鍵都會被清理掉。
一個選擇器鍵包含有兩個準(zhǔn)備好的操作集合,包括感興趣的事件集合instrest和就緒的操作集合ready,通過掩碼保存
感興趣的事件集合interestOps()
通常一個鍵的instrest注冊時就已經(jīng)確認(rèn),但是可以在注冊后通過interestOps(newOps)傳入一個新的ops來改變這個值
channel.register(this.selector, SelectionKey.OP_READ);
上面的代碼注冊的鍵interest包含read事件,可以在對通道IO異步處理時,改變這個ops來臨時取消對read事件的關(guān)注,以防止重復(fù)處理未處理完的通道
就緒的操作集合readyOps()
通過這個方法返回就緒的操作,isReadable( ),isWritable( ),isConnectable( ), 和isAcceptable( )用來判斷這些操作是否就緒,進(jìn)行下一步的處理
示范選擇器的使用
下面列舉兩個示例來示范選擇器的使用
單選擇器單線程
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; /** * 初始化服務(wù)器 * * @throws IOException */ public void init() throws IOException { //設(shè)置服務(wù)器地址端口 SocketAddress address = new InetSocketAddress(this.ip, this.port); //創(chuàng)建服務(wù)端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //綁定服務(wù)器地址 serverSocketChannel.bind(address); //設(shè)置為非阻塞模式 serverSocketChannel.configureBlocking(false); //創(chuàng)建一個選擇器 this.selector = Selector.open(); //將服務(wù)器通道注冊到選擇器中,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 重復(fù)調(diào)用,處理完事件后及時恢復(fù) key.interestOps(key.interestOps() & (~SelectionKey.OP_READ)); //讀取消息 read(key); } iterator.remove(); } } } /** * 恢復(fù)鍵的感興趣事件 * @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); //如果是阻塞通道進(jìn)行注冊,會拋出 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 通道概念選擇器使用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java NIO 通道選擇器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA中Web項(xiàng)目控制臺亂碼的問題及解決方法
這篇文章主要介紹了IDEA中Web項(xiàng)目控制臺亂碼的問題及解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08淺析Java的Hibernate框架中的緩存和延遲加載機(jī)制
這篇文章主要介紹了Java的Hibernate框架中的緩存和延遲加載機(jī)制,Hibernate是注明的Java下SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-11-11Mybatis配置錯誤:java.lang.ExceptionInInitializerError
這篇文章主要介紹了Mybatis配置錯誤:java.lang.ExceptionInInitializerError的相關(guān)資料,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12Spring配置文件解析之BeanDefinitionParserDelegate詳解
這篇文章主要介紹了Spring配置文件解析之BeanDefinitionParserDelegate詳解,對于Spring的配置文件的解析處理操作是在BeanDefinitionParserDelegate中進(jìn)行處理操作,接下來我們簡單介紹一下BeanDefinitionParserDelegate所做的處理操作,需要的朋友可以參考下2024-02-02從零搭建Spring Boot腳手架整合OSS作為文件服務(wù)器的詳細(xì)教程
這篇文章主要介紹了從零搭建Spring Boot腳手架整合OSS作為文件服務(wù)器的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08MyBatis-Plus實(shí)現(xiàn)條件查詢的三種格式例舉詳解
本文主要介紹了MyBatis-Plus三中條件查詢格式的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08