Netty 4.x 寫(xiě)個(gè)時(shí)間服務(wù)器

2018-10-26 09:50 更新

寫(xiě)個(gè)時(shí)間服務(wù)器

在這個(gè)部分被實(shí)現(xiàn)的協(xié)議是 TIME 協(xié)議。和之前的例子不同的是在不接受任何請(qǐng)求時(shí)他會(huì)發(fā)送一個(gè)含32位的整數(shù)的消息,并且一旦消息發(fā)送就會(huì)立即關(guān)閉連接。在這個(gè)例子中,你會(huì)學(xué)習(xí)到如何構(gòu)建和發(fā)送一個(gè)消息,然后在完成時(shí)關(guān)閉連接。

因?yàn)槲覀儗?huì)忽略任何接收到的數(shù)據(jù),而只是在連接被創(chuàng)建發(fā)送一個(gè)消息,所以這次我們不能使用 channelRead() 方法了,代替他的是,我們需要覆蓋 channelActive() 方法,下面的就是實(shí)現(xiàn)的內(nèi)容:

    public class TimeServerHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelActive(final ChannelHandlerContext ctx) { // (1)
            final ByteBuf time = ctx.alloc().buffer(4); // (2)
            time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));

            final ChannelFuture f = ctx.writeAndFlush(time); // (3)
            f.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) {
                    assert f == future;
                    ctx.close();
                }
            }); // (4)
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    }

1.channelActive() 方法將會(huì)在連接被建立并且準(zhǔn)備進(jìn)行通信時(shí)被調(diào)用。因此讓我們?cè)谶@個(gè)方法里完成一個(gè)代表當(dāng)前時(shí)間的32位整數(shù)消息的構(gòu)建工作。

2.為了發(fā)送一個(gè)新的消息,我們需要分配一個(gè)包含這個(gè)消息的新的緩沖。因?yàn)槲覀冃枰獙?xiě)入一個(gè)32位的整數(shù),因此我們需要一個(gè)至少有4個(gè)字節(jié)的 ByteBuf。通過(guò) ChannelHandlerContext.alloc() 得到一個(gè)當(dāng)前的ByteBufAllocator,然后分配一個(gè)新的緩沖。

3.和往常一樣我們需要編寫(xiě)一個(gè)構(gòu)建好的消息。但是等一等,flip 在哪?難道我們使用 NIO 發(fā)送消息時(shí)不是調(diào)用 java.nio.ByteBuffer.flip() 嗎?ByteBuf 之所以沒(méi)有這個(gè)方法因?yàn)橛袃蓚€(gè)指針,一個(gè)對(duì)應(yīng)讀操作一個(gè)對(duì)應(yīng)寫(xiě)操作。當(dāng)你向 ByteBuf 里寫(xiě)入數(shù)據(jù)的時(shí)候?qū)懼羔樀乃饕蜁?huì)增加,同時(shí)讀指針的索引沒(méi)有變化。讀指針?biāo)饕蛯?xiě)指針?biāo)饕謩e代表了消息的開(kāi)始和結(jié)束。

比較起來(lái),NIO 緩沖并沒(méi)有提供一種簡(jiǎn)潔的方式來(lái)計(jì)算出消息內(nèi)容的開(kāi)始和結(jié)尾,除非你調(diào)用 flip 方法。當(dāng)你忘記調(diào)用 flip 方法而引起沒(méi)有數(shù)據(jù)或者錯(cuò)誤數(shù)據(jù)被發(fā)送時(shí),你會(huì)陷入困境。這樣的一個(gè)錯(cuò)誤不會(huì)發(fā)生在 Netty 上,因?yàn)槲覀儗?duì)于不同的操作類型有不同的指針。你會(huì)發(fā)現(xiàn)這樣的使用方法會(huì)讓你過(guò)程變得更加的容易,因?yàn)槟阋呀?jīng)習(xí)慣一種沒(méi)有使用 flip 的方式。

另外一個(gè)點(diǎn)需要注意的是 ChannelHandlerContext.write() (和 writeAndFlush() )方法會(huì)返回一個(gè) ChannelFuture 對(duì)象,一個(gè) ChannelFuture 代表了一個(gè)還沒(méi)有發(fā)生的 I/O 操作。這意味著任何一個(gè)請(qǐng)求操作都不會(huì)馬上被執(zhí)行,因?yàn)樵?Netty 里所有的操作都是異步的。舉個(gè)例子下面的代碼中在消息被發(fā)送之前可能會(huì)先關(guān)閉連接。


    Channel ch = ...;
    ch.writeAndFlush(message);
    ch.close();

因此你需要在 write() 方法返回的 ChannelFuture 完成后調(diào)用 close() 方法,然后當(dāng)他的寫(xiě)操作已經(jīng)完成他會(huì)通知他的監(jiān)聽(tīng)者。請(qǐng)注意,close() 方法也可能不會(huì)立馬關(guān)閉,他也會(huì)返回一個(gè)ChannelFuture。

4.當(dāng)一個(gè)寫(xiě)請(qǐng)求已經(jīng)完成是如何通知到我們?這個(gè)只需要簡(jiǎn)單地在返回的 ChannelFuture 上增加一個(gè)ChannelFutureListener。這里我們構(gòu)建了一個(gè)匿名的 ChannelFutureListener 類用來(lái)在操作完成時(shí)關(guān)閉 Channel。

或者,你可以使用簡(jiǎn)單的預(yù)定義監(jiān)聽(tīng)器代碼:

    f.addListener(ChannelFutureListener.CLOSE);

為了測(cè)試我們的time服務(wù)如我們期望的一樣工作,你可以使用 UNIX 的 rdate 命令

    $ rdate -o <port> -p <host>

Port 是你在main()函數(shù)中指定的端口,host 使用 locahost 就可以了。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)