Netty源碼分析-02 Netty快速入門

在學習netty源碼之前胰耗,應該對netty的基本用法有所了解振亮,由于netty大多數(shù)時候用于開發(fā)服務器端程序,因此下面以一個時間服務器為例穗泵,演示Netty的基本使用,并對主要概念進行介紹谜疤。

2.1 服務器啟動程序

時間服務器很簡單佃延,每次收到QUERY TIME ORDER請求后返回當前時間。

  1. main方法中通過ServerBootstrap啟動netty服務器
//創(chuàng)建兩個線程組,專門用于網(wǎng)絡事件的處理夷磕,Reactor線程組
//一個用來接收客戶端的連接苇侵,
//一個用來進行SocketChannel的網(wǎng)絡讀寫
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
        
try{
        //輔助啟動類
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup,workGroup) // 注冊兩個線程組
            .channel(NioServerSocketChannel.class)//創(chuàng)建的channel為NioServerSocketChannel【nio-ServerSocketChannel】
            .option(ChannelOption.SO_BACKLOG, 1024) // 設置TCP屬性
            .childOption(ChannelOption.SO_KEEPALIVE, true) //配置與cient連接的channel屬性
            .childHandler(new ChildChannelHandler());//處理IO事件的處理類,處理網(wǎng)絡事件
    ChannelFuture f = b.bind(port).sync();//綁定端口后同步等待
    f.channel().closeFuture().sync();//阻塞等待
}catch(Exception e){
    e.printStackTrace();
}finally{
    bossGroup.shutdownGracefully();
    workGroup.shutdownGracefully();
}
  1. 定義ChannelInitializer企锌,會在ServerChannel注冊到事件循環(huán)后觸發(fā)initChannel事件榆浓。
// ChannelHandler初始化處理器
class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new TimeServerHandler());
    }
}
  1. TimeServerHandler 里面負責處理業(yè)務邏輯,發(fā)送當前時間
public class TimeServerHandler extends ChannelInboundHandlerAdapter  {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        ByteBuf buf=(ByteBuf) msg;//將msg轉換成Netty的ByteBuf對象
        byte[] req=new byte[buf.readableBytes()];
        buf.readBytes(req); 
        String body=new String(req,"GBK");
        System.out.println("The time server receive order : "+body);
        String currentTime="QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(System.currentTimeMillis()).toString():"BAD ORDER";
        ByteBuf resp=Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(resp);//只是寫入緩沖區(qū)

    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();//通過網(wǎng)絡發(fā)送
    }
}

2.2 過程解析

  1. 創(chuàng)建輔助啟動類ServerBootstrap撕攒,并設置相關配置:
  • group() 設置處理Accept事件和讀寫操作的事件循環(huán)組
  • channel() 設置通道類型為NioServerSocketChannel陡鹃,這是netty自己定義的Channel烘浦,指的是服務器通道,內部包含java原生的服務器端通道ServerSocketChannel萍鲸,相應的還有客戶端通道NioSocketChannel闷叉。
  • option()/childOption() 設置服務器通道的選項和建立連接后的客戶端通道的選項
  • childHandler() 設置子處理器,內部需要將用戶自定義的處理器加入到netty的pipeline中脊阴,這涉及到Channel握侧,ChannelHandler和Pipeline嘿期,后續(xù)會有講解 品擎。

2.調用bind()方法綁定端口,sync()會阻塞等待處理請求备徐。這是因為bind()方法是一個異步過程萄传,會立即返回一個ChannelFuture對象,調用sync()會等待執(zhí)行完成蜜猾。

3.獲得Channel的closeFuture阻塞等待關閉秀菱,服務器Channel關閉時closeFuture會完成并釋放。

Future的使用參見 Netty源碼分析-04 Future和Promise

2.3 相關概念

在學習Netty的源碼之前蹭睡,需要對Netty的主要概念進行了解衍菱,主要是初步明白每個類負責的任務是什么,能夠完成哪些工作肩豁。當然运悲,每個概念的具體實現(xiàn)會在后續(xù)章節(jié)中進行介紹矾瑰。

Channel

這里的Channel與Java的Channel不是同一個伸眶,是netty自己定義的通道饺蚊;Netty的Channel是對網(wǎng)絡連接處理的抽象竭宰,負責與網(wǎng)絡進行通訊雨膨,支持NIO和OIO兩種方式留量;內部與網(wǎng)絡socket連接吼具,通過channel能夠進行I/O操作雄人,如讀从橘、寫、連接和綁定础钠。

通過Channel可以執(zhí)行具體的I/O操作恰力,如read, write, connect, 和bind。在Netty中旗吁,所有I/O操作都是異步的踩萎;Netty的服務器端處理客戶端連接的Channel創(chuàng)建時可以設置父Channel。例如:ServerSocketChannel接收到請求創(chuàng)建SocketChannel很钓,SocketChannel的父為ServerSocketChannel香府。

ChannelHandler與ChannelPipeline

ChannelHandler是通道處理器董栽,用來處理I/O事件或攔截I/O操作,ChannelPipeline字如其名企孩,是一個雙向流水線锭碳,內部維護了多個ChannelHandler,服務器端收到I/O事件后勿璃,每次順著ChannelPipeline依次調用ChannelHandler的相關方法擒抛。

ChannelHandler是個接口,通常我們在Netty中需要使用下面的子類:

  • ChannelInboundHandler 用來處理輸入的I/O事件
  • ChannelOutboundHandler 用來處理輸出的I/O事件

另外补疑,下面的adapter類提供了

  • ChannelInboundHandlerAdapter 用來處理輸入的I/O事件
  • ChannelOutboundHandlerAdapter 用來處理輸出的I/O事件
  • ChannelDuplexHandler 可以用來處理輸入和輸出的I/O事件

Netty的ChannelPipeline和ChannelHandler機制類似于Servlet和Filter過濾器/攔截器歧沪,每次收到請求會依次調用配置好的攔截器鏈。Netty服務器收到消息后癣丧,將消息在ChannelPipeline中流動和傳遞槽畔,途經(jīng)的ChannelHandler會對消息進行處理,ChannelHandler分為兩種inbound和outbound胁编,服務器read過程中只會調用inbound的方法厢钧,write時只尋找鏈中的outbound的Handler。
ChannelPipeline內部維護了一個雙向鏈表嬉橙,Head和Tail分別代表表頭和表尾早直,Head作為總入口和總出口,負責底層的網(wǎng)絡讀寫操作市框;用戶自己定義的ChannelHandler會被添加到鏈表中霞扬,這樣就可以對I/O事件進行攔截和處理;這樣的好處在于用戶可以方便的通過新增和刪除鏈表中的ChannelHandler來實現(xiàn)不同的業(yè)務邏輯枫振,不需要對已有的ChannelHandler進行修改喻圃。

NettyPipeline

如圖所示,在服務器初始化后粪滤,ServerSocketChannel的會創(chuàng)建一個Pipeline斧拍,內部維護了ChannelHanlder的雙向鏈表,讀取數(shù)據(jù)時杖小,會依次調用ChannelInboundHandler子類的channelRead()方法肆汹,例如:讀取到客戶端數(shù)據(jù)后,依次調用解碼-業(yè)務邏輯-直到Tail予权。
而寫入數(shù)據(jù)時昂勉,會從用戶自定義的ChannelHandler出發(fā)查找ChannelOutboundHandler的子類,調用channelWrite()扫腺,最終由Head的write()向socket寫入數(shù)據(jù)岗照。例如:寫入數(shù)據(jù)會通過業(yè)務邏輯的組裝--編碼--寫入socket(Head的write)。

EventLoop與EventLoopGroup

EventLoop是事件循環(huán),EventLoopGroup是運行在線程池中的事件循環(huán)組谴返,Netty使用了Reactor模型煞肾,服務器的連接和讀寫放在線程池之上的事件循環(huán)中執(zhí)行,這是Netty獲得高性能的原因之一嗓袱。事件循環(huán)內部會打開selector籍救,并將Channel注冊到事件循環(huán)中,事件循環(huán)不斷的進行select()查找準備就緒的描述符渠抹;此外蝙昙,某些系統(tǒng)任務也會被提交到事件循環(huán)組中運行。

ServerBootstrap

ServerBootstrap是輔助啟動類梧却,用于服務端的啟動奇颠,內部維護了很多用于啟動和建立連接的屬性。包括:

  • EventLoopGroup group 線程池組
  • channel是通道
  • channelFactory 通道工廠放航,用于創(chuàng)建channel
  • localAddress 本地地址
  • options 通道的選項烈拒,主要是TCP連接的屬性
  • attrs 用來設置channel的屬性,
  • handler 通道處理器

2.4 啟動過程

了解了上面的概念后广鳍,我們再來根據(jù)程序說明一下服務器的啟動過程荆几,主要分為四個階段:

  • 配置config:設置啟動器/服務器通道/客戶端通道等相關配置;
  • 初始化init:主要功能是打開java的serversocketchannel,內部會初始化Netty的Channel及其ChannelPipeline赊时;
  • 注冊register:將初始化后的Netty-Channel注冊到事件循環(huán)的selector上面吨铸。具體過程:將打開netty的Channel注冊到線程池組的selector上;觸發(fā)Pipeline上面ChannelHandler的channelRegistered祖秒,至此注冊完畢诞吱;
  • 綁定bind:將java的ServerSocketChannel綁定到本地的端口上面,結束后使用fireChannelActive通知Pipeline里的ChannelHandle竭缝,執(zhí)行其channelActive方法房维;

由于注冊階段是異步的,綁定階段會與之同時進行抬纸,因此注冊階段完畢后會判斷綁定階段是否結束從而觸發(fā)channelActive咙俩。在啟動完畢后,會建立下圖中的連接結構:

NettyPipeline

Netty的Channel一端與java的Channel相連接松却,可以進行網(wǎng)絡I/O操作;另一端與Pipeline連接溅话,用來執(zhí)行業(yè)務邏輯晓锻。一旦事件循環(huán)組中的EventLoop在循環(huán)中select()到準備就緒的I/O描述符后,就會交給NettyChannel處理飞几,NettyChannel交給Pipeline的鏈表進行業(yè)務邏輯處理砚哆。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屑墨,隨后出現(xiàn)的幾起案子躁锁,更是在濱河造成了極大的恐慌纷铣,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件战转,死亡現(xiàn)場離奇詭異搜立,居然都是意外死亡,警方通過查閱死者的電腦和手機槐秧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門啄踊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刁标,你說我怎么就攤上這事颠通。” “怎么了膀懈?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵顿锰,是天一觀的道長。 經(jīng)常有香客問我启搂,道長硼控,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任狐血,我火速辦了婚禮淀歇,結果婚禮上,老公的妹妹穿的比我還像新娘匈织。我一直安慰自己浪默,他們只是感情好,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布缀匕。 她就那樣靜靜地躺著纳决,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乡小。 梳的紋絲不亂的頭發(fā)上阔加,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機與錄音满钟,去河邊找鬼胜榔。 笑死,一個胖子當著我的面吹牛湃番,可吹牛的內容都是我干的夭织。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼吠撮,長吁一口氣:“原來是場噩夢啊……” “哼尊惰!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤弄屡,失蹤者是張志新(化名)和其女友劉穎题禀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膀捷,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡迈嘹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了担孔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片江锨。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖糕篇,靈堂內的尸體忽然破棺而出啄育,到底是詐尸還是另有隱情,我是刑警寧澤拌消,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布挑豌,位于F島的核電站,受9級特大地震影響墩崩,放射性物質發(fā)生泄漏氓英。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一鹦筹、第九天 我趴在偏房一處隱蔽的房頂上張望铝阐。 院中可真熱鬧,春花似錦铐拐、人聲如沸徘键。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吹害。三九已至,卻和暖如春虚青,著一層夾襖步出監(jiān)牢的瞬間它呀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工棒厘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纵穿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓奢人,卻偏偏與公主長得像谓媒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子达传,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內容