首頁 > 軟體

Netty與NIO超詳細講解

2022-08-01 18:01:36

Linux下的五種I/O模型

1)阻塞I/O(blocking I/O)

2)非阻塞I/O (nonblocking I/O)

3) I/O複用(select 和poll) (I/O multiplexing)

4)訊號驅動I/O (signal driven I/O (SIGIO))

5)非同步I/O (asynchronous I/O (the POSIX aio_functions))

前面四種都是同步io、第五種是非同步IO;

阻塞式:當程式沒有獲取到資料的時候,整個應用可能會產生阻塞,放棄了CPU執行,無法去做其他事情。

非阻塞式:不管有沒有獲取到資料,都必須立馬返回一個結果,如果沒有獲取到資料的情況下返回一個錯誤標記,根據錯誤的標記不斷輪詢。BIO屬於阻塞式的io操作。可以使用多執行緒實現非同步I0,同時處理多個請求。缺點:非常消耗伺服器資源CPU

阻塞IO的流程

BIO屬於阻塞式的io操作。

可以使用多執行緒實現非同步IO,同時處理多個請求。缺點:非常消耗伺服器資源CPU

當我們在呼叫一個io函數的時候,如果沒有獲取到資料的情況下,那麼就會一直等待;等待的過程中會導致整個應用程式一直是一個阻塞的過程,無法去做其他的實現。

IO複用

IO複用機制:IO實際指的就是網路的IO、多路也就是多個不同的tcp連線;複用也就是指使用同一個執行緒合併處理多個不同的IO操作,這樣的話可以減少CPU資源。

訊號驅動I/O

發出一個請求實現觀察監聽,當有資料的時候直接走我們非同步回撥;

非同步IO

非同步io也就是發出請求資料之後,剩下的事情完全實現非同步完成

同步與非同步

站在多執行緒的角度總結:

同步整個應用程式碼執行順序是從上往下執行 並且返回到結果;

非同步:開啟多個不同的分支實現並行執行 每個執行緒互不影響;

站在web專案角度分析

預設的情況Http請求就是一個同步形式實現呼叫 基於請求與響應,如果我們響應非常耗時的話,會導致使用者端一直等待(使用者體驗非常不好)

NIO

Java的nio是在Jdk1.4版本之後推出了一套新的io方案,這種io方案對原有io做了一次效能上的升級

NIO翻譯成英文 no blocking io 簡稱為 nio 非阻塞io,不是new io。

比傳統的io支援了面向緩衝區、基於通道實現的io的方案。

BIO 與 NIO 區別:

Bio是一個阻塞式的io,它是西向與流傳輸也就是根據每個位元組實現傳輸效率非常低,

而我們的nio是雨向與緩衝區的非阻塞邊.其中最大的亮點:運多路複用機制,

I0多路複用

多路實際上指的是多個不同的 tcp 連線。

複用:一個執行緒可以維護多個不同的io操作。

優點:佔用cpu資源非常小、保證執行緒安全問題。

IO 多路複用實現原理

使用一個Java案例來描述IO多路複用的思路:

public class SocketNioTcpServer {
    private static List<SocketChannel> listSocketChannel = new ArrayList<>();
    private static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
    public static void main(String[] args) {
        try {
            // 1.建立一個ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 2. 繫結地址
            ServerSocketChannel bind = serverSocketChannel.bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel != null) {
                    socketChannel.configureBlocking(false);
                    listSocketChannel.add(socketChannel);
                }
                for (SocketChannel scl : listSocketChannel) {
                    try {
                        int read = scl.read(byteBuffer);
                        if (read > 0) {
                            byteBuffer.flip();
                            Charset charset = Charset.forName("UTF-8");
                            String receiveText = charset.newDecoder().decode
                                    (byteBuffer.asReadOnlyBuffer()).toString();
                            System.out.println("receiveText:" + receiveText);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

NIO核心元件

通道(Channel)

通常我們nio所有的操作都是通過通道開始的,所有的通道都會註冊到統一個選擇器(Selector)上實現管理,在通過選擇器將資料統一寫入到 buffer中。

緩衝區(Buffer)

Buffer本質上就是一塊記憶體區,可以用來讀取資料,也就先將資料寫入到緩衝區中、在統一的寫入到硬碟上。

選擇器(Selector)

Selector可以稱做為選擇器,也可以把它叫做多路複用器,可以在單執行緒的情況下可以去維護多個Channel,也可以去維護多個連線;

使用Java原生API實現NIO操作

public class NIOServer {
    /**
     * 建立一個選擇器
     */
    private Selector selector;
    public void initServer(int port) throws IOException {
        // 獲得一個ServerSocketChannel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 設定通道為非阻塞
        serverSocketChannel.configureBlocking(false);
        // 將該通道對應的ServerSocket繫結到port埠
        serverSocketChannel.bind(new InetSocketAddress(port));
        // 獲得一個通道管理器
        this.selector = Selector.open();
        // 將通道管理器和該通道繫結,併為該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後,
        // 當該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }
    public void listen() throws IOException {
        System.out.println("伺服器端啟動成功!");
        // 輪詢存取selector
        while (true) {
            // 當註冊的事件到達時,方法返回;否則,該方法會一直阻塞
            int select = selector.select();
            if (select == 0) {
                continue;
            }
            // 獲得selector中選中的項的迭代器,選中的項為註冊的事件
            Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 刪除已選的key,以防重複處理
                ite.remove();

                if (key.isAcceptable()) {// 使用者端請求連線事件
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    // 獲得和使用者端連線的通道
                    SocketChannel channel = server.accept();
                    // 設定成非阻塞
                    channel.configureBlocking(false);
                    // 在和使用者端連線成功之後,為了可以接收到使用者端的資訊,需要給通道設定讀的許可權。
                    channel.register(this.selector, SelectionKey.OP_READ);

                } else if (key.isReadable()) {// 獲得了可讀的事件
                    read(key);
                }

            }

        }
    }
    public void read(SelectionKey key) throws IOException {
        // 伺服器可讀取訊息:得到事件發生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 建立讀取的緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(512);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("伺服器端收到資訊:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8"));
        channel.write(outBuffer);// 將訊息回送給使用者端
    }
    public static void main(String[] args) throws IOException {
        NIOServer server = new NIOServer();
        server.initServer(8000);
        server.listen();
    }
}

Redis為什麼支援高並行

Redis官方沒有windows版本redis,只有linux版本的reids。

Redis的底層是採用nio 多路io複用機制實現對多個不同的連線(tcp)實現io的複用;能夠非常好的支援高並行,同時能夠先天性支援執行緒安全的問題。為什麼現場安全?因為使用一個執行緒維護多個不同的io操作 原理使用nio的選擇器,將多個不同的Channel統一交給我們的selector(選擇器管理)。

但是nio的實現在不同的作業系統上存在差別:在我們windows作業系統上使用 select 實現輪訓機制、在linux作業系統使用epoll

備註:windows作業系統是沒有epoll

在windows作業系統中使用select實現輪訓機制時間複雜度是為 o(n),而且這種情況也會存在空輪訓的情況,效率非常低、其次預設對我們的輪訓有一定限制,所以這樣的話很難支援上萬tcp連線。

所以在這時候linux操作就出現epoll實現事件驅動回撥形式通知,不會存在空輪訓的情況,只是對活躍的socket實現主動回撥,這樣的效能有很大的提升 所以時間複雜度為是o(1)

注意:windows作業系統沒有epoll、只有linux作業系統有。

所以為什麼Nginx、redis能夠支援非常高的並行 最終都是靠的linux版本的 io 多路複用機制epoll

到此這篇關於Netty與NIO超詳細講解的文章就介紹到這了,更多相關Netty NIO內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com