netty中的傳輸

終於在課設的閒時間把netty實戰的四五章給解決了

這裏來記錄一下第四章裏面所講的IO

首先說到IO,我想,必須要先了解阻塞,非阻塞,同步和異步這四個詞

看到一個講的很易懂的例子:https://www.cnblogs.com/George1994/p/6702084.html

那麼瞭解完這四個詞,就到了IO了

傳統的IO,即阻塞IO

也就是跟之前用socket編程那樣,沒有數據寫入到來這邊的線程就一直等待,直到數據到來然後再對數據進行處理,例如打印

拿出以前的老代碼

public void bio(int port) throws IOException{
        final ServerSocket socket = new ServerSocket(port);
        try {
            //死循環,直到有連接再用線程將內容寫入
            while (true){
                Socket client = socket.accept();//阻塞
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        OutputStream out;
                        try {
                            out = client.getOutputStream();
                            out.write("hello world".getBytes(CharsetUtil.UTF_8));
                            out.flush();
                            client.close();
                        }catch (IOException e){
                            e.printStackTrace();
                        }finally {
                            try {
                                client.close();
                            }catch (IOException e){
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
傳統阻塞IO

代碼很容易懂,就是等客戶端連接就往裏面寫入數據,每一個連接對應一個線程去處理,索性採用了多線程,如果是單線程,系統就會阻塞在這裏,

多線程CPU在這裏能釋放做更多事情,的確是個很好的模型,至少我之前一直是這麼覺得的...但是似乎這裏忽略了一些問題,那就是連接數量的

問題,如果是小量的連接數還是好的,但是如果往大了增加連接數,打個比方一萬,那麼就會有一萬個線程,那麼問題就顯而易見了,內存這上面就

已經有瓶頸了,而且,線程數量增大,線程之間的切換也會成爲一個大問題,因爲線程切換的成本很高,有可能線程切換的時間比執行的時間還長,

導致更嚴重的問題

所以可以知道的是,如果連接量小,那麼這個模型是沒問題的,如果大了,那麼這個就無能爲力了

 

再來說非阻塞IO,即NIO

這是netty實戰上的非阻塞IO代碼

public void serve(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);//channel設置爲非阻塞模式
        ServerSocket ss = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        ss.bind(address);
        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        for (;;){
            try {
                selector.select();//這個函數是阻塞的
            } catch (IOException ex) {
                ex.printStackTrace();
                //handle exception
                break;
            }
            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    if (key.isAcceptable()) {
                        ServerSocketChannel server =
                                (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_WRITE |
                                SelectionKey.OP_READ, msg.duplicate());//註冊到selector中
                        System.out.println(
                                "Accepted connection from " + client);
                    }
                    if (key.isWritable()) {
                        SocketChannel client =
                                (SocketChannel) key.channel();
                        ByteBuffer buffer =
                                (ByteBuffer) key.attachment();
                        while (buffer.hasRemaining()) {
                            if (client.write(buffer) == 0) {
                                break;
                            }
                        }
                        client.close();
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                        // ignore on close
                    }
                }
            }
        }
    }
View Code

註冊當這幾個事件到來的時候所對應的處理器。然後在合適的時機告訴事件選擇器:對此事件感興趣。對於寫操作,就是寫不出去的時候對

寫事件感興趣;對於讀操作,就是完成連接和系統沒有辦法承載新讀入的數據的時,對於accept,一般是服務器剛啓動的時候;而對於connect,

一般是connect失敗需要重連或者異步調用connect的時候,NIO的讀寫函數可以立刻返回,這就不用另開線程去等待結果了,如果一個連接不能讀

寫(socket.read()返回0或者socket.write()返回0),就把這件事記下來,記錄的方式通常是在Selector上註冊標記位,然後切換到其它就緒的連

接(channel)繼續進行讀寫。

這樣的優點:相比於阻塞模型,非阻塞不用再等待任務,而是把時間花費到其它任務上,也就是這個當前線程同時處理多個任務,但是也有缺點:

那就是導致任務完成的響應延遲增大了,因爲每隔一段時間纔去執行詢問的動作,但是任務萬一在兩次詢問之間的時間間隔內完成,就會導致整體

數據吞吐量的降低。影響用戶體驗

 

Netty中的阻塞和非阻塞

以上是java 裏面的NIO,可以看到從阻塞改寫到非阻塞這其中還是有很多地方要改的,勢必會給帶來很多麻煩,下面繼續回到netty

而在netty中就不會出現這樣的事情了,因爲只需改變EventLoopGroup的種類,分別對應的OioEventLoopGroup和NioEventLoppGroup

和channel的種類,對應OioServerSocketChannel和NioServerSocketChannel,其餘地方基本不變

 

Epool,專用於linux的本地非阻塞傳輸

jdk在linuxl中的NOI的實現上採用了不同的辦法,就是使用了epoll調用,他有着比select和poll更好的性能,同時也是linux非阻塞網絡編程的事實標準

而且如果程序是運行在linux,也不需要改動很多代碼,就跟上面的例子一樣,將NioEventLoopGroup和NioServerSocketChannel改成EpollEventLoopGroup

和EpollServerSocketChannel,這一改動在高負載的情況下能比NIO有更好的表現

 

JVM內部的傳輸

用於在同一個虛擬機中的客戶端和服務端的通信,這個過程中的SocketAddress沒有綁定具體的物理網絡地址,只要服務器在運行,他就會存儲在註冊表裏,channel關閉

即從表裏註銷,這裏的傳輸不接收網絡流量,其實也能猜出來,畢竟本地,所以他不能和其他我們使用的socket傳輸互相操作,所以如果客戶端想使用本機,即同JVM中的服

務端就必須通過他實現

 

小結筆記:

IO訪問過程:(網絡IO本質上就是socket的讀取)

 

 

 對於socket流,就是等待網絡上的數據到達,然後複製到圖中的緩衝區,然後複製到應用進程

 

 

 

 JVM內部通信還有其他幾種IO那裏還沒來得及找例子做一做,找時間再補上,如果錯誤,還請多多指出

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