怎么通俗的理解Netty呢?


Netty(3.X)

有了Netty,你可以實現自己的HTTP服務器,FTP服務器,UDP服務器,RPC服務器,WebSocket服務器,Redis的Proxy服務器,MySQL的Proxy服務器等等。

如果你想知道Nginx是怎么寫出來的,如果你想知道Tomcat和Jetty是如何實現的,如果你也想實現一個簡單的Redis服務器,那都應該好好理解一下Netty,它們高性能的原理都是類似的。

看一下傳統的HTTP服務器的原理:

  1. 創建一個ServerSocket,監聽並綁定一個端口

  2. 一系列客戶端來請求這個端口

  3. 服務器使用Accept,獲得一個來自客戶端的Socket連接對象

  4. 啟動一個新線程處理連接

    1. 讀Socket,得到字節流
    2. 解碼協議,得到Http請求對象
    3. 處理Http請求,得到一個結果,封裝成一個HttpResponse對象
    4. 編碼協議,將結果序列化字節流
    5. 寫Socket,將字節流發給客戶端
  5. 繼續循環步驟3

HTTP服務器之所以稱為HTTP服務器,是因為編碼解碼協議是HTTP協議,如果協議是Redis協議,那它就成了Redis服務器,如果協議是WebSocket,那它就成了WebSocket服務器,等等。

使用Netty你就可以定制編解碼協議,實現自己的特定協議的服務器。

上面我們說的是一個傳統的多線程服務器,這個也是Apache處理請求的模式。在高並發環境下,線程數量可能會創建太多,操作系統的任務調度壓力大,系統負載也會比較高。那怎么辦呢?

於是NIO誕生了,NIO並不是Java獨有的概念,NIO代表的一個詞匯叫着IO多路復用。它是由操作系統提供的系統調用,早期這個操作系統調用的名字是select,但是性能低下,后來漸漸演化成了Linux下的epoll和Mac里的kqueue。我們一般就說是epoll,因為沒有人拿蘋果電腦作為服務器使用對外提供服務。而Netty就是基於Java NIO技術封裝的一套框架。為什么要封裝,因為原生的Java NIO使用起來沒那么方便,而且還有臭名昭著的bug,Netty把它封裝之后,提供了一個易於操作的使用模式和接口,用戶使用起來也就便捷多了。

那NIO究竟是什么東西呢?

NIO的全稱是NoneBlocking IO,非阻塞IO,區別與BIO,BIO的全稱是Blocking IO,阻塞IO。那這個阻塞是什么意思呢?

  1. Accept是阻塞的,只有新連接來了,Accept才會返回,主線程才能繼
  2. Read是阻塞的,只有請求消息來了,Read才能返回,子線程才能繼續處理
  3. Write是阻塞的,只有客戶端把消息收了,Write才能返回,子線程才能繼續讀取下一個請求

所以傳統的多線程服務器是BlockingIO模式的,從頭到尾所有的線程都是阻塞的。這些線程就干等在哪里,占用了操作系統的調度資源,什么事也不干,是浪費。

那么NIO是怎么做到非阻塞的呢?

它用的是事件機制。它可以用一個線程把Accept,讀寫操作,請求處理的邏輯全干了。如果什么事都沒得做,它也不會死循環,它會將線程休眠起來,直到下一個事件來了再繼續干活,這樣的一個線程稱之為NIO線程。

while true {
    events = takeEvents(fds)  // 獲取事件,如果沒有事件,線程就休眠
    for event in events {
        if event.isAcceptable {
            doAccept() // 新鏈接來了
        } elif event.isReadable {
            request = doRead() // 讀消息
            if request.isComplete() {
                doProcess()
            }
        } elif event.isWriteable {
            doWrite()  // 寫消息
        }
    }
}

Netty是建立在NIO基礎之上,Netty在NIO之上又提供了更高層次的抽象。

在Netty里面,Accept連接可以使用單獨的線程池去處理,讀寫操作又是另外的線程池來處理。

Accept連接和讀寫操作也可以使用同一個線程池來進行處理。而請求處理邏輯既可以使用單獨的線程池進行處理,也可以跟放在讀寫線程一塊處理。線程池中的每一個線程都是NIO線程。用戶可以根據實際情況進行組裝,構造出滿足系統需求的並發模型。

Netty提供了內置的常用編解碼器,包括行編解碼器[一行一個請求],前綴長度編解碼器[前N個字節定義請求的字節長度],可重放解碼器[記錄半包消息的狀態],HTTP編解碼器,WebSocket消息編解碼器等等

Netty提供了一些列生命周期回調接口,當一個完整的請求到達時,當一個連接關閉時,當一個連接建立時,用戶都會收到回調事件,然后進行邏輯處理。

Netty可以同時管理多個端口,可以使用NIO客戶端模型,這些對於RPC服務是很有必要的。

Netty除了可以處理TCP Socket之外,還可以處理UDP Socket。

在消息讀寫過程中,需要大量使用ByteBuffer,Netty對ByteBuffer在性能和使用的便捷性上都進行了優化和抽象。

本質:

1)JBoss做的一個Jar包。

2)目的:快速開發高性能、高可靠性的網絡服務器和客戶端程序。

3)優點:提供異步的、事件驅動的網絡應用程序框架和工具。

簡單體驗

public void run() {
        // Configure the server.
        ServerBootstrap bootstrap = new ServerBootstrap(
                new NioServerSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));

        // Set up the pipeline factory.
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                return Channels.pipeline(new EchoServerHandler());
            }
        });

        // Bind and start to accept incoming connections.
        bootstrap.bind(new InetSocketAddress(port));
    }

這里EchoServerHandler是其業務邏輯的實現者,大致代碼如下:

public class EchoServerHandler extends SimpleChannelUpstreamHandler {

	    @Override
	    public void messageReceived(
	            ChannelHandlerContext ctx, MessageEvent e) {
	        // Send back the received message to the remote peer.
	        e.getChannel().write(e.getMessage());
	    }

Netty的事件驅動機制

看看EchoServerHandler的代碼,其中的參數:public void messageReceived(ChannelHandlerContext ctx, MessageEvent e),MessageEvent就是一個事件。這個事件攜帶了一些信息,例如這里e.getMessage()就是消息的內容,而EchoServerHandler則描述了處理這種事件的方式。一旦某個事件觸發,相應的Handler則會被調用,並進行處理。這種事件機制在UI編程里廣泛應用,而Netty則將其應用到了網絡編程領域。

在Netty里,所有事件都來自ChannelEvent接口,這些事件涵蓋監聽端口、建立連接、讀寫數據等網絡通訊的各個階段。而事件的處理者就是ChannelHandler,這樣,不但是業務邏輯,連網絡通訊流程中底層的處理,都可以通過實現ChannelHandler來完成了。事實上,Netty內部的連接處理、協議編解碼、超時等機制,都是通過handler完成的。

下圖描述了Netty進行事件處理的流程。Channel是連接的通道,是ChannelEvent的產生者,而ChannelPipeline可以理解為ChannelHandler的集合。

Netty的源碼閱讀

org
└── jboss
    └── netty
		├── bootstrap 配置並啟動服務的類
		├── buffer 緩沖相關類,對NIO Buffer做了一些封裝
		├── channel 核心部分,處理連接
		├── container 連接其他容器的代碼
		├── example 使用示例
		├── handler 基於handler的擴展部分,實現協議編解碼等附加功能
		├── logging 日志
		└── util 工具類

除了之前說到的事件驅動機制之外,Netty的核心功能還包括兩部分:

  • Zero-Copy-Capable Rich Byte Buffer

    零拷貝的Buffer。為什么叫零拷貝?因為在數據傳輸時,最終處理的數據會需要對單個傳輸層的報文,進行組合或者拆分。NIO原生的ByteBuffer無法做到這件事,而Netty通過提供Composite(組合)和Slice(切分)兩種Buffer來實現零拷貝。這部分代碼在org.jboss.netty.buffer包中。
    這里需要額外注意,不要和操作系統級別的Zero-Copy混淆了, 操作系統中的零拷貝主要是用戶空間和內核空間之間的數據拷貝, NIO中通過DirectBuffer做了實現.

  • Universal Communication API

    統一的通訊API。這個是針對Java的Old I/O和New I/O,使用了不同的API而言。Netty則提供了統一的API(org.jboss.netty.channel.Channel)來封裝這兩種I/O模型。這部分代碼在org.jboss.netty.channel包中。

此外,Protocol Support功能通過handler機制實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM