<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-7  評論-23  文章-0  trackbacks-0
    Netty提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序[官方定義],整體來看其包含了以下內容:1.提供了豐富的協議編解碼支持,2.實現自有的buffer系統,減少復制所帶來的消耗,3.整套channel的實現,4.基于事件的過程流轉以及完整的網絡事件響應與擴展,5.豐富的example。本文并不對Netty實際使用中可能出現的問題做分析,只是從代碼角度分析它的架構以及實現上的一些關鍵細節。

    首先來看下最如何使用Netty(其自帶example很好展示了使用),Netty普通使用一般是通過BootStrap來啟動,BootStrap主要分為兩類:1.面向連接(TCP)的BootStrap(ClientBootStrap和ServerBootstrap),2.非面向連接(UDP)的(ConnectionlessBootstrap)。

    Netty
    整體架構很清晰的分成2個部分,ChannelFactory和ChannelPipelineFactory,前者主要生產網絡通信相關的Channel實例和ChannelSink實例,Netty提供的ChannelFactory實現基本能夠滿足絕大部分用戶的需求,當然你也可以定制自己的ChannelFactory,后者主要關注于具體傳輸數據的處理,同時也包括其他方面的內容,比如異常處理等等,只要是你希望的,你都可以往里添加相應的handler,一般ChannelPipelineFactory由用戶自己實現,因為傳輸數據的處理及其他操作和業務關聯比較緊密,需要自定義處理的handler。

    現在,使用Netty的步驟實際上已經非常明確了,比如面向連接的Netty服務端客戶端使用,第一步:實例化一個BootStrap,并且通過構造方法指定一個ChannelFactory實現,第二步:向bootstrap實例注冊一個自己實現的ChannelPipelineFactory,第三步:如果是服務器端,bootstrap.bind(new InetSocketAddress(port)),然后等待客戶端來連接,如果是客戶端,bootstrap.connect(new InetSocketAddress(host,port))取得一個future,這個時候Netty會去連接遠程主機,在連接完成后,會發起類型為CONNECTED的ChannelStateEvent,并且開始在你自定義的Pipeline里面流轉,如果你注冊的handler有這個事件的響應方法的話那么就會調用到這個方法。在此之后就是數據的傳輸了。下面是一個簡單客戶端的代碼解讀。

    // 實例化一個客戶端Bootstrap實例,其中NioClientSocketChannelFactory實例由Netty提供
            ClientBootstrap bootstrap = new ClientBootstrap(
                    
    new NioClientSocketChannelFactory(
                            Executors.newCachedThreadPool(),
                            Executors.newCachedThreadPool()));

            
    // 設置PipelineFactory,由客戶端自己實現
            bootstrap.setPipelineFactory(new FactorialClientPipelineFactory(count));

            
    //向目標地址發起一個連接
            ChannelFuture connectFuture =
                bootstrap.connect(
    new InetSocketAddress(host, port));

            
    // 等待鏈接成功,成功后發起的connected事件將會使handler開始發送信息并且等待messageRecive,當然這只是示例。
            Channel channel = connectFuture.awaitUninterruptibly().getChannel();

            
    // 得到用戶自定義的handler
            FactorialClientHandler handler =
                (FactorialClientHandler) channel.getPipeline().getLast();

            
    // 從handler里面取數據并且打印,這里需要注意的是,handler.getFactorial使用了從結果隊列result take數據的阻塞方法,而結果隊列會在messageRecieve事件發生時被填充接收回來的數據
            System.err.format(
                    
    "Factorial of %,d is: %,d", count, handler.getFactorial());

    Netty提供了NIO與BIO(OIO)兩種模式處理這些邏輯,其中NIO主要通過一個BOSS線程處理等待鏈接的接入,若干個WORKER線程(從worker線程池中挑選一個賦給Channel實例,因為Channel實例持有真正的java網絡對象)接過BOSS線程遞交過來的CHANNEL進行數據讀寫并且觸發相應事件傳遞給pipeline進行數據處理,而BIO(OIO)方式服務器端雖然還是通過一個BOSS線程來處理等待鏈接的接入,但是客戶端是由主線程直接connect,另外寫數據C/S兩端都是直接主線程寫,而數據讀操作是通過一個WORKER 線程BLOCK方式讀取(一直等待,直到讀到數據,除非channel關閉)。

    網絡動作歸結到最簡單就是服務器端bind->accept->read->write,客戶端connect->read->write,一般bind或者connect后會有多次read、write。這種特性導致,bind,accept與read,write的線程分離,connect與read、write線程分離,這樣做的好處就是無論是服務器端還是客戶端吞吐量將有效增大,以便充分利用機器的處理能力,而不是卡在網絡連接上,不過一旦機器處理能力充分利用后,這種方式反而可能會因為過于頻繁的線程切換導致性能損失而得不償失,并且這種處理模型復雜度比較高。


    采用什么樣的網絡事件響應處理機制對于網絡吞吐量是非常重要的,Netty采用的是標準的SEDA(Staged Event-Driven Architecture)架構[http://en.wikipedia.org/wiki/ Staged_event-driven_architecture],其所設計的事件類型,代表了網絡交互的各個階段,并且在每個階段發生時,觸發相應事件交給初始化時生成的pipeline實例進行處理。事件處理都是通過Channels類的靜態方法調用開始的,將事件、channel傳遞給channel持有的Pipeline進行處理,Channels類幾乎所有方法都為靜態,提供一種Proxy的效果(整個工程里無論何時何地都可以調用其靜態方法觸發固定的事件流轉,但其本身并不關注具體的處理流程)。

    Channels
    部分事件流轉靜態方法
    1.fireChannelOpen  2.fireChannelBound  3.fireChannelConnected  4.fireMessageReceived  5.fireWriteComplete 6.fireChannelInterestChanged
    7.fireChannelDisconnected 8.fireChannelUnbound 9.fireChannelClosed 10.fireExceptionCaught 11.fireChildChannelStateChanged

    Netty提供了全面而又豐富的網絡事件類型,其將java中的網絡事件分為了兩種類型Upstream和Downstream。一般來說,Upstream類型的事件主要是由網絡底層反饋給Netty的,比如messageReceived,channelConnected等事件,而Downstream類型的事件是由框架自己發起的,比如bind,write,connect,close等事件。



    NettyUpstreamDownstream網絡事件類型特性也使一個Handler分為了3種類型,專門處理Upstream,專門處理Downstream,同時處理Upstream,Downstream。實現方式是某個具體Handler通過繼承ChannelUpstreamHandlerChannelDownstreamHandler類來進行區分。PipeLineDownstream或者Upstream類型的網絡事件發生時,會調用匹配事件類型的Handler響應這種調用。ChannelPipeline維持有所有handler有序鏈表,并且由handler自身控制是否繼續流轉到下一個handler(ctx.sendDownstream(e),這樣設計有個好處就是隨時終止流轉,業務目的達到無需繼續流轉到下一個handler)。下面的代碼是取得下一個處理Downstream事件的處理器。

    1DefaultChannelHandlerContext realCtx = ctx;
    2while (!realCtx.canHandleUpstream()) {
    3    realCtx = realCtx.next;
    4    if (realCtx == null{
    5        return null;
    6    }

    7}

    8
    9return realCtx;

    如果是一個網絡會話最末端的事件,比如messageRecieve,那么可能在某個handler里面就直接結束整個會話,并把數據交給上層應用,但是如果是網絡會話的中途事件,比如connect事件,那么當觸發connect事件時,經過pipeline流轉,最終會到達掛載pipeline最底下的ChannelSink實例中,這類實例主要作用就是發送請求和接收請求,以及數據的讀寫操作。



    NIO方式ChannelSink一般會有1BOSS實例(implements Runnable),以及若干個worker實例(不設置默認為cpu cores*2worker),這在前面已經提起過,BOSS線程在客戶端類型的ChannelSink和服務器端類型的ChannelSink觸發條件不一樣,客戶端類型的BOSS線程是在發生connect事件時啟動,主要監聽connect是否成功,如果成功,將啟動一個worker線程,connectedchannel交給這個線程繼續下面的工作,而服務器端的BOSS線程是發生在bind事件時啟動,它的工作也相對比較簡單,對于channel.socket().accept()進來的請求向Nioworker進行工作分配即可。這里需要提到的是,Server端ChannelSink實現比較特別,無論是NioServerSocketPipelineSink還是OioServerSocketPipelineSink的eventSunk方法實現都將channel分為ServerSocketChannel和SocketChannel分開處理。這主要原因是Boss線程accept()一個新的連接生成一個SocketChannel交給Worker進行數據接收。

    1   public void eventSunk(
    2            ChannelPipeline pipeline, ChannelEvent e) throws Exception {
    3        Channel channel = e.getChannel();
    4        if (channel instanceof NioServerSocketChannel) {
    5            handleServerSocket(e);
    6        }
     else if (channel instanceof NioSocketChannel) {
    7            handleAcceptedSocket(e);
    8        }

    9    }

    1 NioWorker worker = nextWorker();
    2                worker.register(new NioAcceptedSocketChannel(
    3                        channel.getFactory(), pipeline, channel,
    4                        NioServerSocketPipelineSink.this, acceptedSocket,
    5                        worker, currentThread), null);

    另外兩者實例化時都會走一遍如下流程:

    1  setConnected();
    2        fireChannelOpen(this);
    3        fireChannelBound(this, getLocalAddress());
    4        fireChannelConnected(this, getRemoteAddress());

    而對應的ChannelSink里面的處理代碼就不同于ServerSocketChannel了,因為走的是handleAcceptedSocket(e)這一塊代碼,從默認實現代碼來說,實例化調用fireChannelOpen(this);fireChannelBound(this,getLocalAddress());fireChannelConnected(this,getRemoteAddress())沒有什么意義,但是對于自己實現的ChannelSink有著特殊意義。具體的用途我沒去了解,但是可以讓用戶插手Server accept連接到準備讀寫數據這一個過程的處理。

     1  switch (state) {
     2            case OPEN:
     3                if (Boolean.FALSE.equals(value)) {
     4                    channel.worker.close(channel, future);
     5                }

     6                break;
     7            case BOUND:
     8            case CONNECTED:
     9                if (value == null{
    10                    channel.worker.close(channel, future);
    11                }

    12                break;
    13            case INTEREST_OPS:
    14                channel.worker.setInterestOps(channel, future, ((Integer) value).intValue());
    15                break;
    16            }


    Netty提供了大量的handler來處理網絡數據,但是大部分是CODEC相關的,以便支持多種協議,下面一個圖繪制了現階段Netty提供的Handlers(紅色部分不完全)



    Netty實現封裝實現了自己的一套ByteBuffer系統,這個ByteBuffer系統對外統一的接口就是ChannelBuffer,這個接口從整體上來說定義了兩類方法,一種是類似getXXX(int index…)setXXX(int index…)需要指定開始操作buffer的起始位置,簡單點來說就是直接操作底層buffer,并不用到Netty特有的高可重用性buffer特性,所以Netty內部對于這類方法調用非常少,另外一種是類似readXXX(),writeXXX()不需要指定位置的buffer操作,這類方法實現放在了AbstractChannelBuffer,其主要的特性就是維持buffer的位置信息,包括readerIndex,writerIndex,以及回溯作用的markedReaderIndexmarkedWriterIndex,當用戶調用readXXX()或者writeXXX()方法時,AbstractChannelBuffer會根據維護的readerIndex,writerIndex計算出讀取位置,然后調用繼承自己的ChannelBuffergetXXX(int index…)或者setXXX(int index…)方法返回結果,這類方法在Netty內部被大量調用,因為這個特性最大的好處就是很方便地重用buffer而不必去費心費力維護index或者新建大量的ByteBuffer

    另外WrappedChannelBuffer接口提供的是對ChannelBuffer的代理,他的用途說白了就是重用底層buffer,但是會轉換一些buffer的角色,比如原本是讀寫皆可 wrapReadOnlyChannelBuffer,那么整個buffer只能使用readXXX()或者getXXX()方法,也就是只讀,然后底層的buffer還是原來那個,再如一個已經進行過讀寫的ChannelBufferwrapTruncatedChannelBuffer,那么新的buffer將會忽略掉被wrapbuffer內數據,并且可以指定新的writeIndex,相當于slice功能。




    Netty實現了自己的一套完整Channel系統,這個channel說實在也是對java 網絡做了一層封裝,加上了SEDA特性(基于事件響應,異步,多線程等)。其最終的網絡通信還是依靠底下的java網絡api。提到異步,不得不提到NettyFuture系統,從channel的定義來說,write,bind,connect,disconnect,unbind,close,甚至包括setInterestOps等方法都會返回一個channelFuture,這這些方法調用都會觸發相關網絡事件,并且在pipeline中流轉。Channel很多方法調用基本上不會馬上就執行到最底層,而是觸發事件,在pipeline中走一圈,最后才在channelsink中執行相關操作,如果涉及網絡操作,那么最終調用會回到Channel中,也就是serversocketchannel,socketchannel,serversocket,socketjava原生網絡api的調用,而這些實例就是jboss實現的channel所持有的(部分channel)



     

    Netty新版本出現了一個特性zero-copy,這個機制可以使文件內容直接傳輸到相應channel上而不需要通過cpu參與,也就少了一次內存復制。Netty內部ChunkedFile FileRegion 構成了non zero-copy zero-copy兩種形式的文件內容傳輸機制,前者需要CPU參與,后者根據操作系統是否支持zero-copy將文件數據傳輸到特定channel,如果操作系統支持,不需要cpu參與,從而少了一次內存復制。ChunkedFile主要使用fileread,readFullyAPI,而FileRegion使用FileChanneltransferTo API2者實現并不復雜。Zero-copy的特性還是得看操作系統的,本身代碼沒有很大的特別之處。

    最后總結下,Netty的架構思想和細節可以說讓人眼前一亮,對于java網絡IO的各個注意點,可以說Netty已經解決得比較完全了,同時Netty的作者也是另外一個NIO框架MINA的作者,在實際使用中積累了豐富的經驗,但是本文也只是一個新手對于Netty的初步理解,還沒有足夠的能力指出某一細節的所發揮的作用。

    posted on 2010-09-25 12:10 BucketLI 閱讀(3514) 評論(1)  編輯  收藏

    評論:
    # re: Netty代碼分析 2010-12-28 16:34 | simaliu
    很不錯的文章,可能這里有一個小筆誤“客戶端connect->read->write”,應該是“客戶端connect->write->read”吧?  回復  更多評論
      

    只有注冊用戶登錄后才能發表評論。


    網站導航:
    博客園   IT新聞   Chat2DB   C++博客   博問  
     
    主站蜘蛛池模板: 2020国产精品亚洲综合网 | 久久99精品视免费看| 亚洲情a成黄在线观看| 亚洲欧美黑人猛交群| 午夜dj在线观看免费视频| 亚洲精品无码久久久久YW| 大陆一级毛片免费视频观看| 亚洲一区二区久久| 毛片免费视频观看| 亚洲狠狠婷婷综合久久| 免费人成视频在线观看视频| 理论亚洲区美一区二区三区| 亚洲高清免费视频| 女同免费毛片在线播放| 亚洲另类激情综合偷自拍| 最近2018中文字幕免费视频| 亚洲一级毛片视频| 情侣视频精品免费的国产| 色爽黄1000部免费软件下载| 国产成人综合亚洲AV第一页 | 亚洲国产美女视频| 无码少妇一区二区浪潮免费| 亚洲成在人线aⅴ免费毛片| www.91亚洲| 精品国产一区二区三区免费| 亚洲无限乱码一二三四区| 永久免费看bbb| GOGOGO免费观看国语| 亚洲美女视频网址| 国产成人精品免费直播| 中文字幕一区二区三区免费视频| 久久精品国产亚洲AV无码娇色| 国产又黄又爽又猛免费app| 欧洲亚洲综合一区二区三区| 亚洲精品卡2卡3卡4卡5卡区| av无码国产在线看免费网站| 二级毛片免费观看全程| 亚洲精品欧洲精品| 日韩亚洲精品福利| 3d成人免费动漫在线观看| 在线亚洲v日韩v|