netty系列之(一)——netty介紹

IO模型

阻塞式IO.png

非阻塞式IO.png

IO復(fù)用.png

信號(hào)驅(qū)動(dòng)式.png

異步IO.png

圖片.png

上述5種IO模型洛波,前4種模型-阻塞IO额获、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO都是同步I/O模型安疗,因?yàn)槠渲姓嬲腎/O操作(recvfrom)將阻塞進(jìn)程,在內(nèi)核數(shù)據(jù)copy到用戶空間時(shí)都是阻塞的够委。

一荐类、NIO原理

Netty 是基于Java NIO 封裝的網(wǎng)絡(luò)通訊框架,只有充分理解了 Java NIO 才能理解好Netty的底層設(shè)計(jì)茁帽。Java NIO 由三個(gè)核心組件組件:
Buffer:固定數(shù)量的數(shù)據(jù)的容器玉罐。在 Java NIO 中,任何時(shí)候訪問 NIO 中的數(shù)據(jù)潘拨,都需要通過緩沖區(qū)(Buffer)進(jìn)行操作吊输。NIO 最常用的緩沖區(qū)則是 ByteBuffer。
Channel:是一個(gè)通道铁追,它就像自來水管一樣季蚂,網(wǎng)絡(luò)數(shù)據(jù)通過 Channel 這根水管讀取和寫入。傳統(tǒng)的 IO 是基于流進(jìn)行操作的琅束,Channle 和流類似癣蟋,但又有些不同:

圖片.png

#傳統(tǒng)IO:FileInputStream
public static void method2(){
       InputStream in = null;
       try{
           in = new BufferedInputStream(new FileInputStream("src/nomal_io.txt"));
 
           byte [] buf = new byte[1024];
           int bytesRead = in.read(buf);
           while(bytesRead != -1)
           {
               for(int i=0;i<bytesRead;i++)
                   System.out.print((char)buf[i]);
               bytesRead = in.read(buf);
           }
       }catch (IOException e)
       {
           e.printStackTrace();
       }finally{
           try{
               if(in != null){
                   in.close();
               }
           }catch (IOException e){
               e.printStackTrace();
           }
       }
   }
#NIO
public static void method1(){
        RandomAccessFile aFile = null;
        try{
            aFile = new RandomAccessFile("src/nio.txt","rw");
            FileChannel fileChannel = aFile.getChannel();
            ByteBuffer buf = ByteBuffer.allocate(1024);
 
            int bytesRead = fileChannel.read(buf);
            System.out.println(bytesRead);
 
            while(bytesRead != -1)
            {
                buf.flip();
                while(buf.hasRemaining())
                {
                    System.out.print((char)buf.get());
                }
 
                buf.compact();
                bytesRead = fileChannel.read(buf);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally{
            try{
                if(aFile != null){
                    aFile.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
//使用Buffer一般遵循下面幾個(gè)步驟:
//分配空間(ByteBuffer buf = ByteBuffer.allocate(1024); 還有一種allocateDirector后面再陳述)
//寫入數(shù)據(jù)到Buffer(int bytesRead = fileChannel.read(buf);)
//調(diào)用filp()方法( buf.flip();)
//從Buffer中讀取數(shù)據(jù)(System.out.print((char)buf.get());)
//調(diào)用clear()方法或者compact()方法

Channel 必須要配合 Buffer 一起使用,通過從 Channel 讀取數(shù)據(jù)到 Buffer 中或者從 Buffer 寫入數(shù)據(jù)到 Channel 中狰闪,如下:

圖片.png

Selector
多路復(fù)用器 Selector疯搅,它是 Java NIO 編程的基礎(chǔ),Selector 提供了詢問Channel是否已經(jīng)準(zhǔn)備好執(zhí)行每個(gè) I/O 操作的能力埋泵。簡單來講幔欧,Selector 會(huì)不斷地輪詢注冊(cè)在其上的 Channel罪治,如果某個(gè) Channel 上面發(fā)生了讀或者寫事件,這個(gè) Channel 就處于就緒狀態(tài)礁蔗,會(huì)被 Selector 輪詢出來觉义,然后通過 SelectionKey 可以獲取就緒 Channel 的集合,進(jìn)行后續(xù)的 I/O 操作浴井。
image.png

  • Acceptor為服務(wù)端Channel注冊(cè)Selector晒骇,監(jiān)聽accept事件
  • 當(dāng)客戶端連接后,觸發(fā)accept事件
  • 服務(wù)器構(gòu)建對(duì)應(yīng)的客戶端Channel磺浙,并在其上注冊(cè)Selector洪囤,監(jiān)聽讀寫事件
  • 當(dāng)發(fā)生讀寫事件后,進(jìn)行相應(yīng)的讀寫處理

TCP服務(wù)端實(shí)例-NIO實(shí)現(xiàn)

NIO客戶端代碼(連接)
//獲取socket通道
SocketChannel channel = SocketChannel.open();        
channel.configureBlocking(false);
//獲得通道管理器
selector=Selector.open();        
channel.connect(new InetSocketAddress(serverIp, port));
//為該通道注冊(cè)SelectionKey.OP_CONNECT事件
channel.register(selector, SelectionKey.OP_CONNECT);

NIO客戶端代碼(監(jiān)聽)
while(true){
    //選擇注冊(cè)過的io操作的事件(第一次為SelectionKey.OP_CONNECT)
   selector.select();
   while(SelectionKey key : selector.selectedKeys()){
       if(key.isConnectable()){
           SocketChannel channel=(SocketChannel)key.channel();
           if(channel.isConnectionPending()){
               channel.finishConnect();//如果正在連接撕氧,則完成連接
           }
           channel.register(selector, SelectionKey.OP_READ);
       }else if(key.isReadable()){ //有可讀數(shù)據(jù)事件瘤缩。
           SocketChannel channel = (SocketChannel)key.channel();
           ByteBuffer buffer = ByteBuffer.allocate(10);
           channel.read(buffer);
           byte[] data = buffer.array();
           String message = new String(data);
           System.out.println("recevie message from server:, size:"
               + buffer.position() + " msg: " + message);
       }
   }
}

NIO服務(wù)端代碼(連接)
//獲取一個(gè)ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.socket().bind(new InetSocketAddress(port));
//獲取通道管理器
selector = Selector.open();
//將通道管理器與通道綁定,并為該通道注冊(cè)SelectionKey.OP_ACCEPT事件伦泥,
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

NIO服務(wù)端代碼(監(jiān)聽)
while(true){
    //當(dāng)有注冊(cè)的事件到達(dá)時(shí)剥啤,方法返回,否則阻塞不脯。
   selector.select();
   for(SelectionKey key : selector.selectedKeys()){
       if(key.isAcceptable()){
           ServerSocketChannel server =
                (ServerSocketChannel)key.channel();
           SocketChannel channel = server.accept();
           channel.write(ByteBuffer.wrap(
            new String("send message to client").getBytes()));
           //在與客戶端連接成功后府怯,為客戶端通道注冊(cè)SelectionKey.OP_READ事件。
           channel.register(selector, SelectionKey.OP_READ);
       }else if(key.isReadable()){//有可讀數(shù)據(jù)事件
           SocketChannel channel = (SocketChannel)key.channel();
           ByteBuffer buffer = ByteBuffer.allocate(10);
           int read = channel.read(buffer);
           byte[] data = buffer.array();
           String message = new String(data);
           System.out.println("receive message from client, size:"
               + buffer.position() + " msg: " + message);
       }
   }
}

二防楷、netty

1牺丙、netty特點(diǎn)

  • 一個(gè)高性能、異步事件驅(qū)動(dòng)的NIO框架域帐,它提供了對(duì)TCP赘被、UDP和文件傳輸?shù)闹С?/li>
  • 使用更高效的socket底層,對(duì)epoll空輪詢引起的cpu占用飆升在內(nèi)部進(jìn)行了處理肖揣,避免了直接使用NIO的陷阱民假,簡化了NIO的處理方式。
  • 采用多種decoder/encoder 支持龙优,對(duì)TCP粘包/分包進(jìn)行自動(dòng)化處理
  • 可使用接受/處理線程池羊异,提高連接效率,對(duì)重連彤断、心跳檢測的簡單支持
  • 可配置IO線程數(shù)野舶、TCP參數(shù), TCP接收和發(fā)送緩沖區(qū)使用直接內(nèi)存代替堆內(nèi)存宰衙,通過內(nèi)存池的方式循環(huán)利用ByteBuf
  • 通過引用計(jì)數(shù)器及時(shí)申請(qǐng)釋放不再引用的對(duì)象平道,降低了GC頻率
  • 使用單線程串行化的方式,高效的Reactor線程模型
  • 大量使用了volitale供炼、使用了CAS和原子類一屋、線程安全類的使用窘疮、讀寫鎖的使用

2、netty線程模型

netty基于Reactor模型冀墨,是對(duì)NIO模型的一種改進(jìn)闸衫。

  • 單線程Reactor模型


    image.png

    這個(gè)模型和上面的NIO流程很類似,只是將消息相關(guān)處理獨(dú)立到了Handler中去了诽嘉!雖然上面說到NIO一個(gè)線程就可以支持所有的IO處理蔚出。但是瓶頸也是顯而易見的!我們看一個(gè)客戶端的情況虫腋,如果這個(gè)客戶端多次進(jìn)行請(qǐng)求骄酗,如果在Handler中的處理速度較慢,那么后續(xù)的客戶端請(qǐng)求都會(huì)被積壓岔乔,導(dǎo)致響應(yīng)變慢酥筝!所以引入了Reactor多線程模型!

  • 多線程Reactor模型


    image.png

    Reactor多線程模型就是將Handler中的IO操作和非IO操作分開滚躯,操作IO的線程稱為IO線程雏门,非IO操作的線程稱為工作線程!這樣的話,客戶端的請(qǐng)求會(huì)直接被丟到線程池中掸掏,客戶端發(fā)送請(qǐng)求就不會(huì)堵塞茁影!

3、netty核心組件

netty服務(wù)端 代碼示例

EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
//EventLoopGroup繼承線程池ScheduledExecutorService
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker);
bootstrap.channel(NioServerSocketChannel.class);//利用反射構(gòu)造NioServerSocketChannel實(shí)例
bootstrap.option(ChannelOption.SO_BACKLOG, 2048);//backlog指定了內(nèi)核為此套接口排隊(duì)的最大連接個(gè)數(shù)
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.handler(new LoggingServerHandler());//handler與childHandler不同
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new MyChannelHandler1());
        ch.pipeline().addLast(new MyChannelHandler2());
        ch.pipeline().addLast(new MyChannelHandler3());
    }
});
ChannelFuture f = bootstrap.bind(port).sync();//bind方法實(shí)現(xiàn)
f.addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                //啟動(dòng)成功
            }
});
f.channel().closeFuture().sync();

class MyChannelHandler1 extends ChannelHandlerAdapter {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        
    }
}

Channel
Channel 是 Netty 網(wǎng)絡(luò)操作抽象類丧凤,它除了包括基本的 I/O 操作募闲,如 bind、connect愿待、read浩螺、write 之外,還包括了 Netty 框架相關(guān)的一些功能仍侥。
EventLoop
Netty 基于事件驅(qū)動(dòng)模型要出,使用不同的事件來通知我們狀態(tài)的改變或者操作狀態(tài)的改變。它定義了在整個(gè)連接的生命周期里當(dāng)有事件發(fā)生的時(shí)候處理的核心抽象农渊。
Channel 為Netty 網(wǎng)絡(luò)操作抽象類患蹂,EventLoop 主要是為Channel 處理 I/O 操作,兩者配合參與 I/O 操作砸紊。

圖片.png

上圖為Channel传于、EventLoop、Thread醉顽、EventLoopGroup之間的關(guān)系沼溜。一個(gè) EventLoop 在它的生命周期內(nèi)只能與一個(gè)Thread綁定,一個(gè) EventLoop 可被分配至一個(gè)或多個(gè) Channel 游添,輪流處理系草。

ChannelFuture
Netty 為異步非阻塞弹惦,即所有的 I/O 操作都為異步的,因此悄但,我們不能立刻得知消息是否已經(jīng)被處理了棠隐。Netty 提供了 ChannelFuture 接口,通過該接口的 addListener() 方法注冊(cè)一個(gè) ChannelFutureListener檐嚣,當(dāng)操作執(zhí)行成功或者失敗時(shí)助泽,監(jiān)聽就會(huì)自動(dòng)觸發(fā)返回結(jié)果。

ChannelFuture f = bootstrap.bind(port).sync();
f.addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                //啟動(dòng)成功
            }
});
f.channel().closeFuture().sync();

ChannelHandler
ChannelHandler 為 Netty 中最核心的組件嚎京,它充當(dāng)了所有處理入站和出站數(shù)據(jù)的應(yīng)用程序邏輯的容器嗡贺。ChannelHandler 主要用來處理各種事件,這里的事件很廣泛鞍帝,比如可以是連接诫睬、數(shù)據(jù)接收、異常帕涌、數(shù)據(jù)轉(zhuǎn)換等摄凡。
ChannelHandler 有兩個(gè)核心子類 ChannelInboundHandler 和 ChannelOutboundHandler,其中 ChannelInboundHandler 用于接收蚓曼、處理入站數(shù)據(jù)和事件亲澡,而 ChannelOutboundHandler 則相反。

ChannelPipeline
ChannelPipeline 為 ChannelHandler 鏈纫版,提供了一個(gè)容器并定義了用于沿著鏈傳播入站和出站事件流的 API床绪。一個(gè)數(shù)據(jù)或者事件可能會(huì)被多個(gè) Handler 處理,在這個(gè)過程中其弊,數(shù)據(jù)或者事件經(jīng)流 ChannelPipeline癞己,由 ChannelHandler 處理。在這個(gè)處理過程中梭伐,一個(gè) ChannelHandler 接收數(shù)據(jù)后處理完成后交給下一個(gè) ChannelHandler痹雅,或者什么都不做直接交給下一個(gè) ChannelHandler。

圖片.png

當(dāng)一個(gè)數(shù)據(jù)流進(jìn)入 ChannlePipeline 時(shí)籽御,它會(huì)從 ChannelPipeline 頭部開始傳給第一個(gè) ChannelInboundHandler 练慕,當(dāng)?shù)谝粋€(gè)處理完后再傳給下一個(gè),一直傳遞到管道的尾部技掏。與之相對(duì)應(yīng)的是铃将,當(dāng)數(shù)據(jù)被寫出時(shí),它會(huì)從管道的尾部開始哑梳,先經(jīng)過管道尾部的 “最后” 一個(gè)ChannelOutboundHandler劲阎,當(dāng)它處理完成后會(huì)傳遞給前一個(gè) ChannelOutboundHandler 。

附錄


TCP.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸠真,一起剝皮案震驚了整個(gè)濱河市悯仙,隨后出現(xiàn)的幾起案子龄毡,更是在濱河造成了極大的恐慌,老刑警劉巖锡垄,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沦零,死亡現(xiàn)場離奇詭異,居然都是意外死亡货岭,警方通過查閱死者的電腦和手機(jī)路操,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來千贯,“玉大人屯仗,你說我怎么就攤上這事∩η矗” “怎么了魁袜?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長敦第。 經(jīng)常有香客問我峰弹,道長,這世上最難降的妖魔是什么申尼? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任垮卓,我火速辦了婚禮垫桂,結(jié)果婚禮上师幕,老公的妹妹穿的比我還像新娘。我一直安慰自己诬滩,他們只是感情好霹粥,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疼鸟,像睡著了一般后控。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上空镜,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天浩淘,我揣著相機(jī)與錄音,去河邊找鬼吴攒。 笑死张抄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的洼怔。 我是一名探鬼主播署惯,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼镣隶!你這毒婦竟也來了极谊?” 一聲冷哼從身側(cè)響起诡右,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轻猖,沒想到半個(gè)月后帆吻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咙边,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年桅锄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片样眠。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡友瘤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出檐束,到底是詐尸還是另有隱情辫秧,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布被丧,位于F島的核電站盟戏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏甥桂。R本人自食惡果不足惜柿究,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黄选。 院中可真熱鬧蝇摸,春花似錦、人聲如沸办陷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽民镜。三九已至啡专,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間制圈,已是汗流浹背们童。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲸鹦,地道東北人慧库。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像亥鬓,于是被迫代替她去往敵國和親晒旅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子雏亚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容