Netty-NIO 詳解、說明、優缺

Liunx IO模型

1、阻塞IO模型

2、非阻塞IO模型

3、IO複用

4、信號驅動式IO

5、異步IO

Liunx IO流程

等待數據準備好

從內核向進程複製數據

阻塞IO模式

非阻塞式IO模型

複用IO模型

信號驅動式I/O模型

異步I/O模型

各種I/O模型的比較

同步、異步

同步I/O操作:導致請求進程阻塞,直到I/O操作完成。

異步I/O操作:不導致請求進程阻塞。

Blocking I/O

package demo07;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class NnServer {
    private int port;

    public NnServer(int port) {
        this.port = port;
    }

    public void startServer() {
        ServerSocket echoServer = null;
        int i = 0;
        System.out.println("服務器在端口[" + this.port + "]等待客戶請求......");
        try {
            echoServer = new ServerSocket(this.port);
            while (true) {
                Socket clientRequest = echoServer.accept();
                handleRequest(clientRequest, i++);
            }
        } catch (IOException e) {
            System.out.println(e);
        }
    }

    private void handleRequest(Socket clientSocket, int clientNo) {
        PrintStream os = null;
        BufferedReader in = null;
        try {
            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            os = new PrintStream(clientSocket.getOutputStream());
            String inputLine;
            while ((inputLine = in.readLine()) != null) {

                // 輸入'Quit'退出
                if (inputLine.equals("Quit")) {
                    System.out.println("關閉與客戶端[" + clientNo + "]......" + clientNo);
                    os.close();
                    in.close();
                    clientSocket.close();
                    break;
                } else {
                    System.out.println("來自客戶端[" + clientNo + "]的輸入: [" + inputLine + "]!");
                    os.println("來自服務器端的響應:" + inputLine);
                }
            }
        } catch (IOException e) {
            System.out.println("Stream closed");
        }
    }

    public static void main(String[] args) throws IOException {
        new NnServer(8080).startServer();
    }
}

package demo07;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class EchoClient {
    public static void main(String[] args) {

        Socket echoSocket = null;
        PrintWriter out = null;
        BufferedReader in = null;

        try {

            echoSocket = new Socket("127.0.0.1", 8080);
            out = new PrintWriter(echoSocket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(
                    echoSocket.getInputStream()));
            System.out.println("連接到服務器......");
            System.out.println("請輸入消息[輸入\"Quit\"]退出:");
            BufferedReader stdIn = new BufferedReader(new InputStreamReader(
                    System.in));
            String userInput;

            while ((userInput = stdIn.readLine()) != null) {
                out.println(userInput);
                System.out.println(in.readLine());

                if (userInput.equals("Quit")) {
                    System.out.println("關閉客戶端......");
                    out.close();
                    in.close();
                    stdIn.close();
                    echoSocket.close();
                    System.exit(1);
                }
                System.out.println("請輸入消息[輸入\"Quit\"]退出:");
            }
        } catch (UnknownHostException e) {
            System.err.println("Don't know about host: PallaviÕs MacBook Pro.");
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for "
                    + "the connection to: PallaviÕs MacBook Pro.");
            System.exit(1);
        }

    }

}

NIO

NIO = New I/O

NIO 1: JSR 51

JDK 1.4引入

http://jcp.org/en/jsr/detail?id=051

NIO 2: JSR203

JDK 7

http://jcp.org/en/jsr/detail?id=203

NIO

Buffers
Channels
Selectors

NIO 2.0

Update
New File System API
Asynchronous I/O

NIO Buffer

一個 Buffer 本質上是內存中的一塊, 可以將數據寫入這塊內存, 從這塊內存獲取數據

java.nio 定義了以下幾個 Buffer 的實現

Java NIO Buffer三大核心概念:position、limit、capacity
最好理解的當然是 capacity,它代表這個緩衝區的容量,一旦設定就不可以更改。比如 capacity 爲 1024 的 IntBuffer,代表其一次可以存放 1024 個 int 類型的值。
一旦 Buffer 的容量達到 capacity,需要清空 Buffer,才能重新寫入值。

從寫操作模式到讀操作模式切換的時候(flip),position 都會歸零,這樣就可以從頭開始讀寫了。
寫操作模式下,limit 代表的是最大能寫入的數據,這個時候 limit 等於 capacity。
寫結束後,切換到讀模式,此時的 limit 等於 Buffer 中實際的數據大小,因爲 Buffer 不一定被寫滿了。

java.nio.buffer,緩衝區抽象

ByteBuffer

理解capacity、limit、position、mark
0 – mark – position – limit – capacity

Direct ByteBuffer VS. non-direct ByteBuffer

Non-direct ByteBuffer

HeapByteBuffer,標準的java類
維護一份byte[]在JVM堆上
創建開銷小

Direct ByteBuffer

底層存儲在非JVM堆上,通過native代碼操作
-XX:MaxDirectMemorySize=<size>
創建開銷大

Buffer創建

1、allocate/allocateDirect方法

2、wrap方法

Buffer讀取

1、put/get方法
2、flip方法
3、mark/reset方法
4、compact方法
5、rewind/clear

Buffer複製 – 淺複製


1、duplicate方法

2、asReadOnlyBuffer方法

3、slice方法

Java NIO Channel
所有的 NIO 操作始於通道,通道是數據來源或數據寫入的目的地,主要地, java.nio 包中主要實現的以下幾個 Channel:

FileChannel:文件通道,用於文件的讀和寫
DatagramChannel:用於 UDP 連接的接收和發送
SocketChannel:把它理解爲 TCP 連接通道,簡單理解就是 TCP 客戶端
ServerSocketChannel:TCP 對應的服務端,用於監聽某個端口進來的請求

Java NIO Selector

Selector是Java NIO中的一個組件,用於檢查一個或多個NIO Channel的狀態是否處於可讀、可寫

如此可以實現單線程管理多個channels,也就是可以管理多個網絡鏈接

java.nio.channels.Selector

支持IO多路複用的抽象實體

註冊Seletable Channel

SelectionKey —— 表示Selector和被註冊的channel之間關係,一份憑證

SelectionKey 保存channel感興趣的事件

Selector.select 更新所有就緒的SelectionKey的狀態,並返回就緒的channel個數

迭代Selected Key集合並處理就緒channel

創建Selector(Creating a Selector)

Selector selector = Selector.open();

註冊Channel到Selector上(Registering Channels with the Selector)

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

register的第二個參數,這個參數是一個“關注集合”,代表關注的channel狀態,
有四種基礎類型可供監聽, 用SelectionKey中的常量表示如下:

SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE

從Selector中選擇channel(Selecting Channels via a Selector)

一旦向Selector註冊了一個或多個channel後,就可以調用select來獲取channel
select方法會返回所有處於就緒狀態的channel
select方法具體如下:

int select()
int select(long timeout)
int selectNow()

select()方法的返回值是一個int,代表有多少channel處於就緒了。也就是自上一次select後有多少channel進入就緒。

selectedKeys()

在調用select並返回了有channel就緒之後,可以通過選中的key集合來獲取channel,這個操作通過調用selectedKeys()方法:

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

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

NIO帶來了什麼

事件驅動模型

避免多線程
單線程處理多任務

非阻塞IO,IO讀寫不再阻塞,而是返回0

基於block的傳輸,通常比基於流的傳輸更高效

更高級的IO函數,zero-copy

IO多路複用大大提高了java網絡應用的可伸縮性和實用性

使用NIO = 高性能

NIO不一定更快的場景

客戶端應用
連接數<1000
併發程度不高
局域網環境下

NIO完全屏蔽了平臺差異(Linux poll/select/epoll, FreeBSD Kqueue)

NIO仍然是基於各個OS平臺的IO系統實現的,差異仍然存在

使用NIO做網絡編程很容易

離散的事件驅動模型,編程困難
陷阱重重
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章