Netty入門教程——認識Netty

什麼是Netty?

Netty 是一個利用 Java 的高級網絡的能力,隱藏其背後的複雜性而提供一個易於使用的 API 的客戶端/服務器框架。
Netty 是一個普遍使用的 Java 網絡編程框架(Netty 在 2011 年得到了Duke's Choice Award,見https://www.java.net/dukeschoice/2011)。它活躍和成長於用戶社區,像大型公司 Facebook 和 Instagram 以及流行 開源項目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其強大的對於網絡抽象的核心代碼。html

以上是摘自《Essential Netty In Action》這本書,本文的內容也是本人讀了這本書以後的一些整理心得,若有不當之處歡迎大蝦們指正java

Netty和Tomcat有什麼區別?

Netty和Tomcat最大的區別就在於通訊協議,Tomcat是基於Http協議的,他的實質是一個基於http協議的web容器,可是Netty不同,他能經過編程自定義各類協議,由於netty可以經過codec本身來編碼/解碼字節流,完成相似redis訪問的功能,這就是netty和tomcat最大的不一樣。linux

有人說netty的性能就必定比tomcat性能高,其實否則,tomcat從6.x開始就支持了nio模式,而且後續還有APR模式——一種經過jni調用apache網絡庫的模式,相比於舊的bio模式,併發性能獲得了很大提升,特別是APR模式,而netty是否比tomcat性能更高,則要取決於netty程序做者的技術實力了。git

爲何Netty受歡迎?

如第一部分所述,netty是一款收到大公司青睞的框架,在我看來,netty可以受到青睞的緣由有三:web

  1. 併發高
  2. 傳輸快
  3. 封裝好

Netty爲何併發高

Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網絡通訊框架,對比於BIO(Blocking I/O,阻塞IO),他的併發性能獲得了很大提升,兩張圖讓你瞭解BIO和NIO的區別:
redis

 
阻塞IO的通訊方式

 
非阻塞IO的通訊方式

從這兩圖能夠看出,NIO的單線程能處理鏈接的數量比BIO要高出不少,而爲何單線程能處理更多的鏈接呢?緣由就是圖二中出現的 Selector
當一個鏈接創建以後,他有兩個步驟要作,第一步是接收完客戶端發過來的所有數據,第二步是服務端處理完請求業務以後返回response給客戶端。NIO和BIO的區別主要是在第一步。
在BIO中,等待客戶端發數據這個過程是阻塞的,這樣就形成了一個線程只能處理一個請求的狀況,而機器能支持的最大線程數是有限的,這就是爲何BIO不能支持高併發的緣由。
而NIO中,當一個Socket創建好以後,Thread並不會阻塞去接受這個Socket,而是將這個請求交給Selector,Selector會不斷的去遍歷全部的Socket,一旦有一個Socket創建完成,他會通知Thread,而後Thread處理完數據再返回給客戶端—— 這個過程是阻塞的,這樣就能讓一個Thread處理更多的請求了。
下面兩張圖是基於BIO的處理流程和netty的處理流程,輔助你理解兩種方式的差異:
 
BIO的處理流程

 
NIO的處理流程

 

除了BIO和NIO以外,還有一些其餘的IO模型,下面這張圖就表示了五種IO模型的處理流程:數據庫


 
五種常見的IO模型
  • BIO,同步阻塞IO,阻塞整個步驟,若是鏈接少,他的延遲是最低的,由於一個線程只處理一個鏈接,適用於少鏈接且延遲低的場景,好比說數據庫鏈接。
  • NIO,同步非阻塞IO,阻塞業務處理但不阻塞數據接收,適用於高併發且處理簡單的場景,好比聊天軟件。
  • 多路複用IO,他的兩個步驟處理是分開的,也就是說,一個鏈接可能他的數據接收是線程a完成的,數據處理是線程b完成的,他比BIO能處理更多請求,可是比不上NIO,可是他的處理性能又比BIO更差,由於一個鏈接他須要兩次system call,而BIO只須要一次,因此這種IO模型應用的很少。
  • 信號驅動IO,這種IO模型主要用在嵌入式開發,不參與討論。
  • 異步IO,他的數據請求和數據處理都是異步的,數據請求一次返回一次,適用於長鏈接的業務場景。

以上摘自Linux IO模式及 select、poll、epoll詳解apache

Netty爲何傳輸快

Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。咱們知道,Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是佔用內存空間最大的一塊,也是Java對象存放的地方,通常咱們的數據若是須要從IO讀取到堆內存,中間須要通過Socket緩衝區,也就是說一個數據會被拷貝兩次才能到達他的的終點,若是數據量大,就會形成沒必要要的資源浪費。
Netty針對這種狀況,使用了NIO中的另外一大特性——零拷貝,當他須要接收數據的時候,他會在堆內存以外開闢一塊內存,數據就直接從IO讀到了那塊內存中去,在netty裏面經過ByteBuf能夠直接對這些數據進行直接操做,從而加快了傳輸速度。
下兩圖就介紹了兩種拷貝方式的區別,摘自Linux 中的零拷貝技術,第 1 部分
編程

 
傳統數據拷貝

 

 
零拷貝

上文介紹的ByteBuf是Netty的一個重要概念,他是netty數據處理的容器,也是Netty封裝好的一個重要體現,將在下一部分作詳細介紹。segmentfault

爲何說Netty封裝好?

要說Netty爲何封裝好,這種用文字是說不清的,直接上代碼:

  • 阻塞I/O
public class PlainOioServer {

    public void serve(int port) throws IOException {
        final ServerSocket socket = new ServerSocket(port);     //1
        try {
            for (;;) {
                final Socket clientSocket = socket.accept();    //2
                System.out.println("Accepted connection from " + clientSocket);

                new Thread(new Runnable() {                        //3
                    @Override
                    public void run() {
                        OutputStream out;
                        try {
                            out = clientSocket.getOutputStream();
                            out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));                            //4
                            out.flush();
                            clientSocket.close();                //5

                        } catch (IOException e) {
                            e.printStackTrace();
                            try {
                                clientSocket.close();
                            } catch (IOException ex) {
                                // ignore on close
                            }
                        }
                    }
                }).start();                                        //6
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

做者:追那個小女孩
連接:https://www.jianshu.com/p/b9f3f6a16911
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

  

  • 非阻塞IO
public class PlainNioServer {
    public void serve(int port) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        ServerSocket ss = serverChannel.socket();
        InetSocketAddress address = new InetSocketAddress(port);
        ss.bind(address);                                            //1
        Selector selector = Selector.open();                        //2
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);    //3
        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
        for (;;) {
            try {
                selector.select();                                    //4
            } catch (IOException ex) {
                ex.printStackTrace();
                // handle exception
                break;
            }
            Set<SelectionKey> readyKeys = selector.selectedKeys();    //5
            Iterator<SelectionKey> iterator = readyKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                try {
                    if (key.isAcceptable()) {                //6
                        ServerSocketChannel server =
                                (ServerSocketChannel)key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        client.register(selector, SelectionKey.OP_WRITE |
                                SelectionKey.OP_READ, msg.duplicate());    //7
                        System.out.println(
                                "Accepted connection from " + client);
                    }
                    if (key.isWritable()) {                //8
                        SocketChannel client =
                                (SocketChannel)key.channel();
                        ByteBuffer buffer =
                                (ByteBuffer)key.attachment();
                        while (buffer.hasRemaining()) {
                            if (client.write(buffer) == 0) {        //9
                                break;
                            }
                        }
                        client.close();                    //10
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                        // 在關閉時忽略
                    }
                }
            }
        }
    }
}

做者:追那個小女孩
連接:https://www.jianshu.com/p/b9f3f6a16911
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

  

  • Netty
public class NettyOioServer {

    public void server(int port) throws Exception {
        final ByteBuf buf = Unpooled.unreleasableBuffer(
                Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
        EventLoopGroup group = new OioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();        //1

            b.group(group)                                    //2
             .channel(OioServerSocketChannel.class)
             .localAddress(new InetSocketAddress(port))
             .childHandler(new ChannelInitializer<SocketChannel>() {//3
                 @Override
                 public void initChannel(SocketChannel ch) 
                     throws Exception {
                     ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {            //4
                         @Override
                         public void channelActive(ChannelHandlerContext ctx) throws Exception {
                             ctx.writeAndFlush(buf.duplicate()).addListener(ChannelFutureListener.CLOSE);//5
                         }
                     });
                 }
             });
            ChannelFuture f = b.bind().sync();  //6
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();        //7
        }
    }
}

做者:追那個小女孩
連接:https://www.jianshu.com/p/b9f3f6a16911
來源:簡書
簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

  

從代碼量上來看,Netty就已經秒殺傳統Socket編程了,可是這一部分博大精深,僅僅貼幾個代碼豈能說明問題,在這裏給你們介紹一下Netty的一些重要概念,讓你們更理解Netty。

  • Channel
    數據傳輸流,與channel相關的概念有如下四個,上一張圖讓你瞭解netty裏面的Channel。


     
    Channel一覽
    • Channel,表示一個鏈接,能夠理解爲每個請求,就是一個Channel。
    • ChannelHandler,核心處理業務就在這裏,用於處理業務請求。
    • ChannelHandlerContext,用於傳輸業務數據。
    • ChannelPipeline,用於保存處理過程須要用到的ChannelHandler和ChannelHandlerContext。
  • ByteBuf
    ByteBuf是一個存儲字節的容器,最大特色就是使用方便,它既有本身的讀索引和寫索引,方便你對整段字節緩存進行讀寫,也支持get/set,方便你對其中每個字節進行讀寫,他的數據結構以下圖所示:
 
ByteBuf數據結構

他有三種使用模式:

  1. Heap Buffer 堆緩衝區
    堆緩衝區是ByteBuf最經常使用的模式,他將數據存儲在堆空間。
  2. Direct Buffer 直接緩衝區
    直接緩衝區是ByteBuf的另一種經常使用模式,他的內存分配都不發生在堆,jdk1.4引入的nio的ByteBuffer類容許jvm經過本地方法調用分配內存,這樣作有兩個好處
    • 經過免去中間交換的內存拷貝, 提高IO處理速度; 直接緩衝區的內容能夠駐留在垃圾回收掃描的堆區之外。
    • DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 以外的內存, GC對此」無能爲力」,也就意味着規避了在高負載下頻繁的GC過程對應用線程的中斷影響.
  3. Composite Buffer 複合緩衝區
    複合緩衝區至關於多個不一樣ByteBuf的視圖,這是netty提供的,jdk不提供這樣的功能。

除此以外,他還提供一大堆api方便你使用,在這裏我就不一一列出了,具體參見ByteBuf字節緩存

  • Codec
    Netty中的編碼/解碼器,經過他你能完成字節與pojo、pojo與pojo的相互轉換,從而達到自定義協議的目的。
    在Netty裏面最有名的就是HttpRequestDecoder和HttpResponseEncoder了。

Netty入門教程2——動手搭建HttpServer
Netty入門教程3——Decoder和Encoder
Netty入門教程4——如何實現長鏈接

以上就是我對《Netty實戰》這本書的一些心得和書外的一些相關知識整理,若是有不一樣的看法,歡迎討論!

做者:追那個小女孩 連接:https://www.jianshu.com/p/b9f3f6a16911 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索