Netty(2) -- 四種IO模型的比較

1、BIO

  傳統的網絡編程是基於BIO模型的,Server服務端提供IP地址與端口號,客戶端向服務端發起請求,經過三次握手建立連接,如果連接成功,雙方就可以通過套接字(Socket)進行通信了。
  我們使用傳統網絡編程進行通信時,大致思路是服務端開啓線程,佔用IP和端口號,開啓accept()方法,等待客戶端的連接。客戶端連接成功後,便開啓網絡輸出流輸出數據。服務端亦是如此,雙方均可使用輸入輸出流進行通信。這種做法無疑是簡單明瞭的,但卻存在極大地問題。第一、傳統網絡編程的一些API均爲造成線程阻塞,如accept(),在服務端未接收到客戶端的請求連接時,此時線程一直阻塞着;還有輸入輸出流的讀寫方法,若爲接受到停止信號,也會一直阻塞線程。着會大大地降低網絡通信的新能。第二、服務端需要對每一個客戶端均創建一個線程,着便極大地限制了傳統網絡編程的發展。如今的互聯網講究的是高併發高性能,若是上萬個客戶端同時訪問,服務端便要創建上萬個線程,這顯然是不允許的。

2、僞異步IO

  爲了解決BIO面臨的一個客戶端就需要一個線程的問題,也就是上述問題的第二點,後來有人對它的線程模型進行了優化,即使用線程池和消息隊列對客戶端請求進行管理。後端通過一個線程池來處理多個客戶端的請求接入,形成客戶端個數M:線程池最大線程數N的比例關係,其中M可以遠遠大於N。通過線程池可以靈活地調配線程資源,設置線程的最大值,防止由於海量併發接入導致線程耗盡。
  當有新的客戶端接入時,將客戶端的Socket 封裝成一個Task(Task實現java.lang.Runnable接口)投遞到後端的線程池中進行處理,JDK的線程池維護一個消息隊列和N個活躍線程,對消息隊列中的任務進行處理.由於線程池可以設置消息隊列的大小和最大線程數。因此,它的費源佔用是可控的,無論多少個客戶端併發訪問,都不會導致資源的耗盡和宕機。
在這裏插入圖片描述
  然而,通過上面對BIO的講述,知道僞異步IO僅僅解決了其中一個問題,還有另外一個問題沒被解決,那就是阻塞問題。當一個請求被阻塞時,排在隊列後面的請求將被永遠阻塞!這在網絡速度較慢或者傳輸內容太多時,是常有發生的。

3、NIO

  與 Socket類和ServerSocket類相對應,NIO也提供了 SocketChannel 和 ServerSocketChannel兩種不同的套接字通道實現.這兩種新増的通道都支持阻塞和非阻塞兩種模式。阻塞模式使用非常簡單,但是性能和可靠性都不好。非阻塞模式則正好相反。一般來說,低負載、低併發的應用程序可以選擇同步阻塞IO以降低編程複雜度;對於高負栽、高併發的網絡應用,需要使用NIO的非阻塞模式進行開發。
  NIO較BIO的進步之處的功能在於它新定義的三個組件:Buffer、Channel、Selector

3.1、Buffer

  Buffer是一個對象,它包含一些要寫入或者要讀出的數據.在NIO類庫中加入Buffer對象,體現了NIO與BIO的一個重要區別。在面向流的I/O中,可以將數據直接寫入或者將數據直接讀到Stream對象中。在NIO中,所有數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的:在寫入數據時,寫入到緩衝區中。任何時候訪問NIO中的數據,都是通過緩衝區進行操作。
  Buffer實際上是一個特殊的數組,它還內置了三個屬性,所以可以跟蹤和記錄緩衝區的狀態變化,進而實現更加複雜的操作。其中,最重要的就是容量(capacity)、界限(limit)、位置(position)
容量:緩衝區能夠容納的數據元素的最大數量,這一個容量在緩衝區被初始化時確定;
界限:緩衝區的第一個不能被讀或寫的元素;
位置:下一個要被讀或寫的元素的索引,位置會自動由相應的 get( )和 put( )函數更新;
標記:可直接將position定位到mark。
在這裏插入圖片描述

3.2、Channel

  Channel是一個通道,它就像自來水管一樣,網絡數據通過Channel讀取和寫入。通道與流的不同之處在於通道是雙向的,流只是在一個方向上移動(一個流必須是Inputstream或者Outputstream的子類),而通道可以用於讀、寫或者二者同時進行。因爲Channel是全雙工的,所以它可以比流更好地映射底層操作系統的API。特別是在UNIX網絡編程模型中,底層操作系統的通道都是全雙工的,同時支持讀寫操作。

3.3、Selector

  Selector(選擇器)能夠檢測多個註冊的通道上是否有事件發生。如果有事件發生,便獲取事件然後針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接。這樣使得只有在連接真正有讀寫事件發生時,纔會調用函數來進行讀寫,就大大地減少了系統開銷,並且不必爲每個連接都創建一個線程,不用去維護多個線程,並 且避免了多線程之間的上下文切換導致的開銷。
  簡單來說,就是一個線程擁有一個Selector,而一個Selector管理多個讀寫IO,這樣即使一個IO沒有進行讀寫操作,也不會造成線程阻塞,導致性能降低。

4、AIO

  NIO 2.0的異步套接字通道是真正的異步非阻塞I/O,對應於UNIX網絡編程中的事件驅動I/O。它不需要通過多路複用器對註冊的通道進行輪詢操作即可實現異步讀寫,從而簡化了NIO的編程模型。

4.1、AIO的簡單案例

服務端 – AsyncTimeServerHandler 類:

package aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.CountDownLatch;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:09
 */
public class AsyncTimeServerHandler implements Runnable {
    private int port;
    CountDownLatch latch;
    AsynchronousServerSocketChannel asynchronousServerSocketChannel;

    public AsyncTimeServerHandler(int port) {
        this.port = port;
        try {
            asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
            asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
            System.out.println("The time server is start in port:" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        latch = new CountDownLatch(1);
        doAccept();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void doAccept() {
        asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
    }
}

服務端 – AcceptCompletionHandler 類

package aio;

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:10
 */
public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel,AsyncTimeServerHandler> {
    @Override
    public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) {
        attachment.asynchronousServerSocketChannel.accept(attachment, this);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        result.read(buffer, buffer, new ReadCompletionHandler(result));
    }

    @Override
    public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
        exc.printStackTrace();
        attachment.latch.countDown();
    }
}

服務端 – ReadCompletionHandler 類

package aio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Date;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 14:26
 */
public class ReadCompletionHandler implements CompletionHandler<Integer,ByteBuffer> {
    private AsynchronousSocketChannel channel;

    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
        if (this.channel == null) {
            this.channel = channel;
        }
    }

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        attachment.flip();
        byte[] body = new byte[attachment.remaining()];
        attachment.get(body);
        try {
            String req = new String(body, "UTF-8");
            System.out.println("The time server receive order:" + req);
            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
            doWrite(currentTime);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private void doWrite(String currentTime) {
        if (currentTime != null && currentTime.trim().length() > 0) {
            byte[] bytes = (currentTime).getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            writeBuffer.put(bytes);
            writeBuffer.flip();

            channel.write(writeBuffer, writeBuffer,new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer buffer) {
                    //如果沒有發送完成,繼續發送
                    if (buffer.hasRemaining()) {
                        channel.write(buffer, buffer, this);
                    }
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服務端 – TimeServer 類:

package aio;

import java.io.IOException;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:16
 */
public class TimeServer {
    public static void main(String[] args) throws IOException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                //
            }
        }

        AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
        new Thread(timeServer,"AIO-AsyncTimeServerHandler-001").start();
    }
}

客戶端 – AsyncTimeClientHandler 類

package aio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:13
 */
public class AsyncTimeClientHandler implements CompletionHandler<Void,AsyncTimeClientHandler>,Runnable {
    private AsynchronousSocketChannel client;
    private String host;
    private int port;
    private CountDownLatch latch;

    public AsyncTimeClientHandler(String host, int port) {
        this.host = host;
        this.port = port;
        try {
            client = AsynchronousSocketChannel.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        latch = new CountDownLatch(1);
        client.connect(new InetSocketAddress(host, port), this, this);
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void completed(Void result, AsyncTimeClientHandler attachment) {
        byte[] req = "QUERY TIME ORDER".getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
        writeBuffer.put(req);
        writeBuffer.flip();
        client.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer buffer) {
                if (buffer.hasRemaining()) {
                    client.write(buffer, buffer, this);
                }else {
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    client.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            buffer.flip();
                            byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            String body;
                            try {
                                body = new String(bytes, "UTF-8");
                                System.out.println("Now is:" + body);
                                latch.countDown();
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            try {
                                client.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    client.close();
                    latch.countDown();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void failed(Throwable exc, AsyncTimeClientHandler attachment) {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客戶端 – TimeClient 類

package aio;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:13
 */
public class TimeClient {
    public static void main(String[] args) {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                //
            }
        }

        new Thread(new AsyncTimeClientHandler("127.0.0.1",port),"AIO-AsyncTimeClientHandler-001").start();
    }
}

5、四種IO的比較

BIO 異步IO NIO AIO
客戶端:IO線程 1:1 M:N(其中M >> N) M:1 M:0
是否阻塞 阻塞 阻塞 非阻塞 非阻塞
是否同步 同步 同步 同步 異步
API使用難度 簡單 簡單 非常複雜 複雜
調試難度 簡單 簡單 複雜 複雜
可靠性 非常差
吞吐量

此博客爲讀書筆記,參考書籍:《Netty權威指南》

有錯誤的地方敬請指出,歡迎大家評論區或者私信交流!每日持續更新Java、Python、大數據技術,請大家多多關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章