欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中IO的NIO通道解析

 更新時間:2024年01月23日 09:05:40   作者:碼靈  
這篇文章主要介紹了Java中IO的NIO通道解析,NIO 提供了與傳統(tǒng) BIO 模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現(xiàn),需要的朋友可以參考下

概述

NIO 中的 N 可以理解為 Non-blocking,一種同步非阻塞的 I/O 模型,在 Java 1.4 中引入,對應的在java.nio包下。

NIO 新增了 Channel、Selector、Buffer 等抽象概念,支持面向緩沖、基于通道的 I/O 操作方法。

NIO 提供了與傳統(tǒng) BIO 模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現(xiàn)。

NIO 這兩種通道都支持阻塞和非阻塞兩種模式。阻塞模式使用就像傳統(tǒng)中的支持一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。

對于低負載、低并發(fā)的應用程序,可以使用同步阻塞 I/O 來提升開發(fā)效率和更好的維護性;對于高負載、高并發(fā)的(網(wǎng)絡)應用,應使用 NIO 的非阻塞模式來開發(fā)。

正文

我們先看一下 NIO 涉及到的核心關聯(lián)類圖,如下:

上圖中有三個關鍵類:Channel 、Selector 和 Buffer,它們是 NIO 中的核心概念。

  • Channel:可以理解為通道;
  • Selector:可以理解為選擇器;
  • Buffer:可以理解為數(shù)據(jù)緩沖流;

NIO 引入了 Channel、Buffer 和 Selector 就是想把 IO 傳輸過程中涉及到的信息具體化,讓程序員有機會去控制它們。

當我們進行傳統(tǒng)的網(wǎng)絡 IO 操作時,比如調(diào)用 write() 往 Socket 中的 SendQ 隊列寫數(shù)據(jù)時,當一次寫的數(shù)據(jù)超過  SendQ 長度時,操作系統(tǒng)會按照 SendQ  的長度進行分割的,這個過程中需要將用戶空間數(shù)據(jù)和內(nèi)核地址空間進行切換,而這個切換不是程序員可以控制的,由底層操作系統(tǒng)來幫我們處理。

而在 Buffer 中,我們可以控制 Buffer 的 capacity(容量),并且是否擴容以及如何擴容都可以控制。

代碼示例

實例圖:

客戶端程序示例:

package org.example.nio.example;
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;
/**
 * NIO客戶端
 */
public class NIOClient {
    // 通道管理器(Selector)
    private static Selector selector;
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建通道管理器(Selector)
        selector = Selector.open();
        // 創(chuàng)建通道SocketChannel
        SocketChannel channel = SocketChannel.open();
        // 將通道設置為非阻塞
        channel.configureBlocking(false);
        // 客戶端連接服務器,其實方法執(zhí)行并沒有實現(xiàn)連接,需要在handleConnect方法中調(diào)channel.finishConnect()才能完成連接
        channel.connect(new InetSocketAddress("127.0.0.1", 9090));
        /**
         * 將通道(Channel)注冊到通道管理器(Selector),并為該通道注冊selectionKey.OP_CONNECT
         * 注冊該事件后,當事件到達的時候,selector.select()會返回,
         * 如果事件沒有到達selector.select()會一直阻塞。
         */
        channel.register(selector, SelectionKey.OP_CONNECT);
        // 循環(huán)處理
        while (true) {
            /*
             * 選擇一組可以進行I/O操作的事件,放在selector中,客戶端的該方法不會阻塞,
             * selector的wakeup方法被調(diào)用,方法返回,而對于客戶端來說,通道一直是被選中的
             * 這里和服務端的方法不一樣,查看api注釋可以知道,當至少一個通道被選中時。
             */
            selector.select();
            // 獲取監(jiān)聽事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 迭代處理
            while (iterator.hasNext()) {
                // 獲取事件
                SelectionKey key = iterator.next();
                // 移除事件,避免重復處理
                iterator.remove();
                // 檢查是否是一個就緒的已經(jīng)連接服務端成功事件
                if (key.isConnectable()) {
                    handleConnect(key);
                } else if (key.isReadable()) {// 檢查套接字是否已經(jīng)準備好讀數(shù)據(jù)
                    handleRead(key);
                }
            }
        }
    }
    /**
     * 處理客戶端連接服務端成功事件
     */
    private static void handleConnect(SelectionKey key) throws IOException {
        // 獲取與服務端建立連接的通道
        SocketChannel channel = (SocketChannel) key.channel();
        if (channel.isConnectionPending()) {
            // channel.finishConnect()才能完成連接
            channel.finishConnect();
        }
        channel.configureBlocking(false);
        // 數(shù)據(jù)寫入通道
        String msg = "Hello Server!";
        channel.write(ByteBuffer.wrap(msg.getBytes()));
        // 通道注冊到選擇器,并且這個通道只對讀事件感興趣
        channel.register(selector, SelectionKey.OP_READ);
    }
    /**
     * 監(jiān)聽到讀事件,讀取客戶端發(fā)送過來的消息
     */
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        // 從通道讀取數(shù)據(jù)到緩沖區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(128);
        channel.read(buffer);
        // 輸出服務端響應發(fā)送過來的消息
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("服務端發(fā)來的消息:" + msg);
    }
}

服務端示例:

package org.example.nio.example;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;
/**
 * NIO服務端
 */
public class NIOServer {
    // 通道管理器(Selector)
    private static Selector selector;
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建通道管理器(Selector)
        selector = Selector.open();
        // 創(chuàng)建通道ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 將通道設置為非阻塞
        serverSocketChannel.configureBlocking(false);
        // 將ServerSocketChannel對應的ServerSocket綁定到指定端口(port)
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(9090));
        /**
         * 將通道(Channel)注冊到通道管理器(Selector),并為該通道注冊selectionKey.OP_ACCEPT事件
         * 注冊該事件后,當事件到達的時候,selector.select()會返回,
         * 如果事件沒有到達selector.select()會一直阻塞。
         */
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 循環(huán)處理
        while (true) {
            // 當注冊事件到達時,方法返回,否則該方法會一直阻塞
            selector.select();
            // 獲取監(jiān)聽事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 迭代處理
            while (iterator.hasNext()) {
                // 獲取事件
                SelectionKey key = iterator.next();
                // 移除事件,避免重復處理
                iterator.remove();
                // 檢查是否是一個就緒的可以被接受的客戶端請求連接
                if (key.isAcceptable()) {
                    handleAccept(key);
                } else if (key.isReadable()) {// 檢查套接字是否已經(jīng)準備好讀數(shù)據(jù)
                    handleRead(key);
                }
            }
        }
    }
    /**
     * 處理客戶端連接成功事件
     */
    private static void handleAccept(SelectionKey key) throws IOException {
        // 獲取客戶端連接通道
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = server.accept();
        socketChannel.configureBlocking(false);
        // 信息通過通道發(fā)送給客戶端
        String msg = "Hello Client!";
        socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
        // 給通道設置讀事件,客戶端監(jiān)聽到讀事件后,進行讀取操作
        socketChannel.register(selector, SelectionKey.OP_READ);
    }
    /**
     * 監(jiān)聽到讀事件,讀取客戶端發(fā)送過來的消息
     */
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        // 從通道讀取數(shù)據(jù)到緩沖區(qū)
        ByteBuffer buffer = ByteBuffer.allocate(128);
        channel.read(buffer);
        // 輸出客戶端發(fā)送過來的消息
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("server received msg from client:" + msg);
    }
}

通道channel

NIO 的核心就是通道和緩存區(qū),所以它們的工作模式是這樣的:

通道有點類似 IO 中的流,但不同的是,同一個通道既允許讀也允許寫,而任意一個流要么是讀流要么是寫流。 但是你要明白一點,通道和流一樣都是需要基于物理文件的,而每個流或者通道都通過文件指針操作文件,這里說的通道是雙向的也是有前提的,那就是通道基于隨機訪問文件RandomAccessFile的可讀可寫文件指針。

基本的通道類型有如下一些:

FileChannel 是基于文件的通道;

SocketChannel 和 ServerSocketChannel 用于網(wǎng)絡 TCP 套接字數(shù)據(jù)報讀寫;

DatagramChannel 是用于網(wǎng)絡 UDP 套接字數(shù)據(jù)報讀寫。

通道不能單獨存在,它永遠需要綁定一個緩存區(qū),所有的數(shù)據(jù)只會存在于緩存區(qū)中,無論你是寫或是讀,必然是緩存區(qū)通過通道到達磁盤文件,或是磁盤文件通過通道到達緩存區(qū)。即緩存區(qū)是數(shù)據(jù)的起點,也是終點。

緩存區(qū)Buffer

緩沖區(qū)(Buffer):一個用于特定基本數(shù)據(jù)類型的容器。由java.nio包定義的,所有緩沖區(qū)都是Buffer抽象類的子類。

Java NIO 中的 Buffer 主要用于和 NIO 通道進行交互,數(shù)據(jù)是從通道讀入到緩沖區(qū)的,然后從緩沖區(qū)中寫入到通道中的。

Buffer 就像一個數(shù)組,可以保存多個相同類型的數(shù)據(jù)。根據(jù)數(shù)據(jù)類型的不同(boolean)除外,有以下 Buffer 常用子類:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。上述 Buffer 類他們都是通過相似的方法進行管理數(shù)據(jù)的,只是各自管理的數(shù)據(jù)類型不同而已。都是通過如下的方法獲取一個 Buffer 對象:

public static XxxBuffer allocate(int capacity) {}

緩沖區(qū)的基本屬性

  • 容量(capacity):表示 Buffer 最大數(shù)據(jù)容量,緩沖區(qū)容量不能為負,并且一旦創(chuàng)建不能更改。
  • 限制(limit):第一個不應該讀取或?qū)懭氲臄?shù)據(jù)的索引,即位于 limit 后的數(shù)據(jù)不可讀寫。緩沖區(qū)的限制不能為負,并且不能大于容量。
  • 位置(position):下一個要讀取或?qū)懭氲臄?shù)據(jù)的索引。緩沖區(qū)的位置不能為負,并且不能大于其限制。
  • 標記(mark)和重置(reset):標記是一個索引,通過Buffer中的mark()方法指定Buffer中的一個特定的position,之后可以通過調(diào)用reset()方法恢復到這個position。

簡而言之:0 <= mark <= position <= limit <= capacity。

Selector(選擇器)

Selector 被稱為選擇器 ,當然你也可以翻譯為多路復用器 。它是Java NIO 核心組件中的一個,用于檢查一個或多個 Channel(通道)的狀態(tài)是否處于連接就緒、接受就緒、可讀就緒、可寫就緒。

如此可以實現(xiàn)單線程管理多個 channels,也就是可以管理多個網(wǎng)絡連接。

使用 Selector 的好處在于: 相比傳統(tǒng)方式使用多個線程來管理 IO,Selector 使用了更少的線程就可以處理通道了,并且實現(xiàn)網(wǎng)絡高效傳輸!

創(chuàng)建一個選擇器一般是通過 Selector 的工廠方法,Selector.open :

Selector selector = Selector.open();

而一個通道想要注冊到某個選擇器中,必須調(diào)整模式為非阻塞模式,例如:

//創(chuàng)建一個 TCP 套接字通道
SocketChannel channel = SocketChannel.open();
//調(diào)整通道為非阻塞模式
channel.configureBlocking(false);
//向選擇器注冊一個通道
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

以上代碼是注冊一個通道到選擇器中的最簡單版本,支持注冊選擇器的通道都有一個 register 方法,該方法就是用于注冊當前實例通道到指定選擇器的。

該方法的第一個參數(shù)就是目標選擇器,第二個參數(shù)其實是一個二進制掩碼,它指明當前選擇器感興趣當前通道的哪些事件。以枚舉類型提供了以下幾種取值:

  • int OP_READ = 1 << 0;
  • int OP_WRITE = 1 << 2;
  • int OP_CONNECT = 1 << 3;
  • int OP_ACCEPT = 1 << 4;

這種用二進制掩碼來表示某些狀態(tài)的機制,我們在講述虛擬機類類文件結(jié)構(gòu)的時候也遇到過,它就是用一個二進制位來描述一種狀態(tài)。

register 方法會返回一個 SelectionKey 實例,該實例代表的就是選擇器與通道的一個關聯(lián)關系。你可以調(diào)用它的 selector 方法返回當前相關聯(lián)的選擇器實例,也可以調(diào)用它的 channel 方法返回當前關聯(lián)關系中的通道實例。

除此之外,SelectionKey 的 readyOps 方法將返回當前選擇感興趣當前通道中事件中準備就緒的事件集合,依然返回的一個整型數(shù)值,也就是一個二進制掩碼。

例如:

int readySet = selectionKey.readyOps();

假如 readySet 的值為 13,二進制 「0000 1101」,從后向前數(shù),第一位為 1,第三位為 1,第四位為 1,那么說明選擇器關聯(lián)的通道,讀就緒、寫就緒,連接就緒。

所以,當我們注冊一個通道到選擇器之后,就可以通過返回的 SelectionKey 實例監(jiān)聽該通道的各種事件。

當然,一旦某個選擇器中注冊了多個通道,我們不可能一個一個的記錄它們注冊時返回的 SelectionKey 實例來監(jiān)聽通道事件,選擇器應當有方法返回所有注冊成功的通道相關的 SelectionKey 實例。

Set<SelectionKey> keys = selector.selectedKeys();

selectedKeys 方法會返回選擇器中注冊成功的所有通道的 SelectionKey 實例集合。我們通過這個集合的 SelectionKey 實例,可以得到所有通道的事件就緒情況并進行相應的處理操作。

總結(jié)

優(yōu)點

1個線程就行就能處理所有連接,這個線程不停循環(huán)遍歷就行了。

缺點

單線程不停循環(huán)發(fā)起系統(tǒng)調(diào)用,一樣會耗盡 CPU 資源。

NIO 的瓶頸

在于需要不停的調(diào)起系統(tǒng)調(diào)用,每個鏈接我們都要調(diào)系統(tǒng)調(diào)用詢問是否有過來數(shù)據(jù),我們要是明確的知道哪個連接有數(shù)據(jù)包過來呢,就不用挨個遍歷尋找找答案了。

到此這篇關于Java中IO的NIO通道解析的文章就介紹到這了,更多相關NIO通道解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java結(jié)合Vue項目打包并進行服務器部署

    Java結(jié)合Vue項目打包并進行服務器部署

    本文主要介紹了Java結(jié)合Vue項目打包并進行服務器部署,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • Java 日期與時間API相關用法總結(jié)

    Java 日期與時間API相關用法總結(jié)

    這篇文章主要介紹了Java 日期與時間API相關用法總結(jié),幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2021-02-02
  • SpringBoot入門之集成JSP的示例代碼

    SpringBoot入門之集成JSP的示例代碼

    這篇文章主要介紹了SpringBoot入門之集成JSP的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • 使用Iterator刪除List中的多個元素操作

    使用Iterator刪除List中的多個元素操作

    這篇文章主要介紹了使用Iterator刪除List中的多個元素操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java中Shiro安全框架的權(quán)限管理

    Java中Shiro安全框架的權(quán)限管理

    這篇文章主要介紹了Java中Shiro安全框架的權(quán)限管理,Apache?Shiro是Java的一個安全框架,Shiro可以非常容易的開發(fā)出足夠好的應用,其不僅可以用在JavaSE環(huán)境,也可以用在JavaEE環(huán)境,需要的朋友可以參考下
    2023-08-08
  • SpringBoot的HandlerInterceptor中依賴注入為null問題

    SpringBoot的HandlerInterceptor中依賴注入為null問題

    這篇文章主要介紹了SpringBoot的HandlerInterceptor中依賴注入為null問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • MyBatis中映射文件的使用案例代碼

    MyBatis中映射文件的使用案例代碼

    這篇文章主要介紹了MyBatis中映射文件的使用,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-02-02
  • spring @Component注解原理解析

    spring @Component注解原理解析

    這篇文章主要介紹了spring @Component注解原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-02-02
  • 關于Nacos配置管理的統(tǒng)一配置管理、自動刷新詳解

    關于Nacos配置管理的統(tǒng)一配置管理、自動刷新詳解

    這篇文章主要介紹了關于Nacos配置管理的統(tǒng)一配置管理、自動刷新詳解,Nacos是阿里的一個開源產(chǎn)品,是針對微服務架構(gòu)中的服務發(fā)現(xiàn)、配置管理、服務治理的綜合型解決方案,需要的朋友可以參考下
    2023-05-05
  • SpringBoot定時調(diào)度之Timer與Quartz詳解

    SpringBoot定時調(diào)度之Timer與Quartz詳解

    Java?中常用的定時調(diào)度框架有以下幾種:Timer、ScheduledExecutorService、Spring?Task和Quartz,本文主要來和大家講講他們的具體使用,需要的可以參考一下
    2023-06-06

最新評論