Android開發(fā)之使用Netty進(jìn)行Socket編程(二)

Android開發(fā)之使用Netty進(jìn)行Socket編程(一) 概括了一些SocketNIO的基本概念颇玷,下面正式介紹開發(fā)中使用到的Netty API以及在Android客戶端中如何使用Netty通過Socket與服務(wù)器交互就缆。

1 Channel

A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind.A channel provides a user:

  1. the current state of the channel (e.g. is it open? is it connected?),
  2. the configuration parameters of the channel (e.g. receive buffer size),
  3. the I/O operations that the channel supports (e.g. read, write, connect, and bind), and
  4. the ChannelPipeline which handles all I/O events and requests associated with the channel.

Java NIO中竭宰,Channel的作用類似于Java IO的Stream(實(shí)際上有所不同,參考上一篇文章)狞甚。文檔里也說的很清楚廓旬,在客戶端與服務(wù)端建立連接后孕豹,網(wǎng)絡(luò)IO操作是在Channel對(duì)象上進(jìn)行的。

2 ChannelHandler

public interface ChannelHandler

Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its ChannelPipeline
.

當(dāng)客戶端與服務(wù)器建立起連接后春霍,ChannelHandler的方法是被網(wǎng)絡(luò)event(這里的event是廣義的)觸發(fā)的叶眉,由ChannelHandler直接處理輸入輸出數(shù)據(jù),并傳遞到管道中的下一個(gè)ChannelHandler中莲趣。
通過Channel或者ChannelHandlerContext發(fā)生的請(qǐng)求/響應(yīng)event 就是在管道中ChannelHandler傳遞喧伞。

ChannelInboundHandler對(duì)從客戶端發(fā)往服務(wù)器的報(bào)文進(jìn)行處理,一般用來執(zhí)行解碼绿聘、讀取客戶端數(shù)據(jù)次舌、進(jìn)行業(yè)務(wù)處理等彼念;ChannelOutboundHandler對(duì)從服務(wù)器發(fā)往客戶端的報(bào)文進(jìn)行處理,一般用來進(jìn)行編碼哲思、發(fā)送報(bào)文到客戶端吩案。
一般就是繼承ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter徘郭,因?yàn)?strong>Adapter把定制(custom) ChannelHandler的麻煩減小到了最低,Adapter本身已經(jīng)實(shí)現(xiàn)了基礎(chǔ)的數(shù)據(jù)處理邏輯(例如將event轉(zhuǎn)發(fā)到下一個(gè)handler)胧后,你可以只重寫那些你想要特別實(shí)現(xiàn)的方法抱环。

示例:

/**
 * Handles a server-side channel.
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter{ // (1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);// (3)
        String body = new String(req, "UTF-8");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

具體使用:

  1. 一般做法是繼承ChannelInboundHandlerAdapter镇草,這個(gè)類實(shí)現(xiàn)了ChannelHandler接口陶夜,ChannelHandler提供了許多事件處理的接口方法,然后你可以覆蓋這些方法∏玻現(xiàn)在僅僅只需要繼承ChannelHandlerAdapter類而不是你自己去實(shí)現(xiàn)接口方法。
  2. 以上我們覆蓋了channelRead()事件處理方法肩袍。每當(dāng)從客戶端收到新的數(shù)據(jù)時(shí)婚惫,這個(gè)方法會(huì)在收到消息時(shí)被調(diào)用先舷,這個(gè)例子中,收到的消息的類型是ByteBuf牲芋。
  3. 和NIO一樣捺球,讀取數(shù)據(jù)時(shí)氮兵,它是直接讀到緩沖區(qū)中;在寫入數(shù)據(jù)時(shí)卜高,它也是寫入到緩沖區(qū)中秩霍。在TCP/IP中铃绒,NETTY會(huì)把讀到的數(shù)據(jù)放到ByteBuf的數(shù)據(jù)結(jié)構(gòu)中螺捐。所以這里讀取在ByteBuf的信息定血,得到服務(wù)器返回的內(nèi)容。

3 ChannelPipeline

public interface ChannelPipeline extends Iterable < Map.Entry < String , ChannelHandler >>

A list of ChannelHandler
s which handles or intercepts inbound events and outbound operations of a Channel
. ChannelPipeline
implements an advanced form of the Intercepting Filter pattern to give a user full control over how an event is handled and how the ChannelHandler
s in a pipeline interact with each other.

ChannelPipeline作為放置ChannelHandler的容器灾票,采用了J2EE的 攔截過濾模式刊苍,用戶可以定義管道中的ChannelHandler以哪種規(guī)則去攔截并處理事件以及在管道中的ChannelHandler之間如何通信。每個(gè)Channel都有它自己的Pipeline啥纸,當(dāng)一個(gè)新的Channel被創(chuàng)建時(shí)會(huì)自動(dòng)被分配到一個(gè)Pipeline中婴氮。
ChannelHandler按如下步驟安裝到ChannelPipeline中:

  1. 一個(gè)ChannelInitializer接口實(shí)現(xiàn)被注冊(cè)到一個(gè)Bootstrap上:
bootstrap.handler(new ChannelInitializer<SocketChannel>() {//(1)
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new LineBasedFrameDecoder(Integer.MAX_VALUE));
                            pipeline.addLast(nettyClientHandler);//自定義的ChannelInboundHandlerAdapter子類
                        }
                    });

注意:上面編碼器(encoders)主经,解碼器(decoders)罩驻,和ChannelInboundHandlerAdapter的子類都屬于ChannelHandler

  1. 當(dāng)ChannelInitializer.initChannel()被調(diào)用時(shí)迷扇,這個(gè)ChannelInitializer會(huì)往管道(pipeline)中安裝定制的一組ChannelHandler
  2. 然后這個(gè)ChannelInitializer把自己從ChannelPipeline中移除

4 NioEventLoopGroup

MultithreadEventLoopGroup
implementations which is used for NIO Selector
based Channel
s.

NioEventLoopGroup繼承了MultithreadEventLoopGroup蜓席,是用來處理NIO操作的多線程事件循環(huán)器课锌,Netty提供了許多不同的EventLoopGroup的實(shí)現(xiàn)用來處理不同傳輸協(xié)議渺贤。
NioEventLoopGroup實(shí)際上就是個(gè)線程池,NioEventLoopGroup在后臺(tái)啟動(dòng)了n個(gè)IO線程(NioEventLoop)來處理Channel事件瞭亮,每一個(gè)NioEventLoop負(fù)責(zé)處理m個(gè)Channel统翩,NioEventLoopGroup從NioEventLoop數(shù)組里挨個(gè)取出NioEventLoop來處理Channel(詳見《NioEventLoopGroup繼承層次結(jié)構(gòu)》)此洲。
相比于服務(wù)端,客戶端只需要?jiǎng)?chuàng)建一個(gè)EventLoopGroup娶桦,因?yàn)樗恍枰?dú)立的線程去監(jiān)聽客戶端連接衷畦,而且Netty是異步事件驅(qū)動(dòng)的NIO框架,它的連接和所有IO操作都是異步的戴差,因此不需要?jiǎng)?chuàng)建單獨(dú)的連接線程铛嘱。

5 Bootstrap

Bootstrap以及 ServerBootstrap類都繼承自 AbstractBootstrap墨吓。官方API文檔中的解釋是:

AbstractBootstrap
is a helper class that makes it easy to bootstrap a Channel
. It support method-chaining to provide an easy way to configure the AbstractBootstrap
.

Bootstrap中文翻譯就是引導(dǎo)程序帖烘,就是作為管理Channel的一個(gè)輔助類≌肇裕可以通過“方法鏈”的代碼形式(類似Builder模式)去配置一個(gè)Bootstrap乡摹,創(chuàng)建Channel并發(fā)起請(qǐng)求。Bootstrap類為一個(gè)應(yīng)用的網(wǎng)絡(luò)層配置提供了容器瞬痘,客戶端通過它來jianjie框全。
示例:

EventLoopGroup group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class)
              .group(group);
              .handler(new ChannelInitializer<SocketChannel>() {//(1)
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new LineBasedFrameDecoder(Integer.MAX_VALUE));
                            pipeline.addLast(nettyClientHandler);//(2)
                        }
                    });
  1. ChannelInitializer是一個(gè)特殊的ChannelHandler類津辩,作用是幫助使用者配置一個(gè)新的Channel筒严。也許你想通過增加一些新的ChannelHandler子類來操作一個(gè)新的Channel或者通過其對(duì)應(yīng)的ChannelPipeline來實(shí)現(xiàn)你的網(wǎng)絡(luò)程序鸭蛙。當(dāng)你的程序變的復(fù)雜時(shí)筋岛,可能會(huì)增加更多的ChannelHandler子類到pipeline上。
  2. 每個(gè)Channel都有ChannelPipeline,在Channel的pipeline中加入handler肪获,這里的ChannelHandler類經(jīng)常會(huì)被用來處理一個(gè)最近的已經(jīng)接收的Channel寝凌。所以這里的Channel已經(jīng)不是NIO中的Channel了,她是netty的Channel孝赫。

6 建立連接并發(fā)起請(qǐng)求

public class NettyClient {
    private Channel channel ;

    public void connect(int port,String host){
        EventLoopGroup group = new NioEventLoopGroup();
        try {//配置Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
            .channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new LineBasedFrameDecoder(Integer.MAX_VALUE));
                            pipeline.addLast(new NettyClientHandler () );
                }
            });

            //發(fā)起異步連接操作
            ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            
             channel = channelFuture.channel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //關(guān)閉较木,釋放線程資源
            group.shutdownGracefully();
        }
    }


  public void sendMessage(String msg){//連接成功后,通過Channel提供的接口進(jìn)行IO操作
   try {
         if (channel != null && channel.isOpen()) {
            channel.writeAndFlush(sendMsg).sync();     //(1)
            Log.d(TAG, "send succeed " + sendMsg);
                        } else {
                            throw new Exception("channel is null | closed");
                        }
                    } catch (Exception e) {
                        sendReconnectMessage();
                        e.printStackTrace();
                    }  
  } 
    @Test
    public void nettyClient(){
        new NettyClient().connect(8989, "localhost");
    }
    
}
  1. Channel 及ChannelHandlerContext對(duì)象提供了許多操作青柄,能夠觸發(fā)各種各樣的I/O事件和操作伐债。
    write(Object)方法不會(huì)使消息寫入到Channel上致开,他被緩沖在了內(nèi)部峰锁,你需要調(diào)用flush()方法來把緩沖區(qū)中數(shù)據(jù)強(qiáng)行輸出∷粒或者可以用更簡(jiǎn)潔的writeAndFlush(msg)以達(dá)到同樣的目的虹蒋。

7 總結(jié)

簡(jiǎn)而言之,在客戶端上使用Netty飒货,業(yè)務(wù)流程如下:

  1. 構(gòu)建Bootstrap魄衅,其中包括設(shè)置好ChannelHandler來處理將來接收到的數(shù)據(jù)。
  2. 由Boostrap發(fā)起連接塘辅。
  3. 連接成功建立后徐绑,得到一個(gè)ChannelFuture對(duì)象,代表了一個(gè)還沒有發(fā)生的I/O操作莫辨。這意味著任何一個(gè)請(qǐng)求操作都不會(huì)馬上被執(zhí)行傲茄,因?yàn)樵贜etty里所有的操作都是異步的。
  4. 通過Channel對(duì)象的writeAndFlush(Object msg)方法往服務(wù)端發(fā)送數(shù)據(jù)沮榜,接收到的數(shù)據(jù) 會(huì)在ChannelHandler的實(shí)現(xiàn)類中的channelRead(ChannelHandlerContext ctx, Object msg)中獲取到被讀到緩沖區(qū)的數(shù)據(jù)——(ByteBuf) msg盘榨。

8 問題

  1. TCP連接中,NETTY會(huì)把讀到的數(shù)據(jù)放到ByteBuf的數(shù)據(jù)結(jié)構(gòu)中蟆融〔菅玻基于流的傳輸并不是一個(gè)數(shù)據(jù)包隊(duì)列,而是一個(gè)字節(jié)隊(duì)列型酥。即使服務(wù)器發(fā)送了2個(gè)獨(dú)立的消息山憨,客戶端也不會(huì)作為2次消息處理而僅僅是作為一連串的字節(jié)進(jìn)行讀取。因此這是不能保證你遠(yuǎn)程寫入的數(shù)據(jù)就會(huì)準(zhǔn)確地讀取弥喉。尤其是 服務(wù)器發(fā)回的消息長(zhǎng)度 過長(zhǎng)的時(shí)候郁竟,一次消息將有可能被拆分到不同的ByteBuf數(shù)據(jù)段中,很有可能需要多次讀取ByteBuf由境,才能把一個(gè)消息完整拿到棚亩。
    解決方案:
    ByteToMessageDecoderChannelHandler的一個(gè)實(shí)現(xiàn)類蓖议,他可以在處理數(shù)據(jù)拆分的問題上變得很簡(jiǎn)單。

相關(guān)鏈接

  1. Netty 5用戶指南
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末讥蟆,一起剝皮案震驚了整個(gè)濱河市勒虾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘸彤,老刑警劉巖修然,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異质况,居然都是意外死亡愕宋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門拯杠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掏婶,“玉大人,你說我怎么就攤上這事潭陪⌒弁祝” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵依溯,是天一觀的道長(zhǎng)老厌。 經(jīng)常有香客問我,道長(zhǎng)黎炉,這世上最難降的妖魔是什么枝秤? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮慷嗜,結(jié)果婚禮上淀弹,老公的妹妹穿的比我還像新娘。我一直安慰自己庆械,他們只是感情好薇溃,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缭乘,像睡著了一般沐序。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上堕绩,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天策幼,我揣著相機(jī)與錄音,去河邊找鬼奴紧。 笑死特姐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绰寞。 我是一名探鬼主播到逊,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼铣口,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼滤钱!你這毒婦竟也來了觉壶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤件缸,失蹤者是張志新(化名)和其女友劉穎铜靶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體他炊,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡争剿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了痊末。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚕苇。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凿叠,靈堂內(nèi)的尸體忽然破棺而出涩笤,到底是詐尸還是另有隱情,我是刑警寧澤盒件,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布蹬碧,位于F島的核電站,受9級(jí)特大地震影響炒刁,放射性物質(zhì)發(fā)生泄漏恩沽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一翔始、第九天 我趴在偏房一處隱蔽的房頂上張望罗心。 院中可真熱鬧,春花似錦城瞎、人聲如沸渤闷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肤晓。三九已至,卻和暖如春认然,著一層夾襖步出監(jiān)牢的瞬間补憾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工卷员, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盈匾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓毕骡,卻偏偏與公主長(zhǎng)得像削饵,于是被迫代替她去往敵國(guó)和親岩瘦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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

  • netty常用API學(xué)習(xí) netty簡(jiǎn)介 Netty是基于Java NIO的網(wǎng)絡(luò)應(yīng)用框架. Netty是一個(gè)NIO...
    花丶小偉閱讀 6,012評(píng)論 0 20
  • 6.2 Channel實(shí)現(xiàn) ![Netty_Channel類圖][2] Channel的類圖比較清晰窿撬。我們主要分析...
    Hypercube閱讀 8,532評(píng)論 6 19
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理启昧,服務(wù)發(fā)現(xiàn),斷路器劈伴,智...
    卡卡羅2017閱讀 134,702評(píng)論 18 139
  • 本文是Netty文集中“Netty in action”系列的文章密末。主要是對(duì)Norman Maurer and M...
    tomas家的小撥浪鼓閱讀 3,869評(píng)論 3 8
  • 昨晚,童童不好好配合刷牙跛璧,他躺在床上不起來严里,我在一旁苦口婆心地引導(dǎo)他主動(dòng)積極配合,就這樣維持了近半個(gè)小時(shí)...
    Shealy222閱讀 97評(píng)論 0 0