Netty實踐-使用POJO代替ByteBuf

到目前爲止,我們上面幾篇教程中的所有例子都使用ByteBuf作爲協議消息的主要數據結構。 在本節中,我們將改進TIME協議的客戶端和服務器示例,讓它們使用POJO來代替原來的ByteBuf。

ChannelHandler中使用POJO的優點是顯而易見的; 處理程序將從ByteBuf中提取信息的代碼,將從處理程序中分離出來,變得更易維護和可重用。 在TIME客戶端和服務器示例中,我們只讀取一個32位整數,它不是直接使用ByteBuf來解碼轉換的。在實現真實世界協議時,這是必須要進行分離的。

首先,我們定義一個名爲 UnixTime 的新類型(一個簡單的Java類)。

package com.yiibai.netty.timepojo;

import java.util.Date;

public class UnixTime {

    private final long value;

    public UnixTime() {
        this(System.currentTimeMillis() / 1000L + 2208988800L);
    }

    public UnixTime(long value) {
        this.value = value;
    }

    public long value() {
        return value;
    }

    @Override
    public String toString() {
        Date date = new Date((value() - 2208988800L) * 1000L);
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateString = formatter.format(date);
        return dateString;
    }
}

我們現在來修改時間解碼器(TimeDecoder)來生成UnixTime,而不是ByteBuf

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    if (in.readableBytes() < 4) {
        return;
    }

    out.add(new UnixTime(in.readUnsignedInt()));
}

使用更新的解碼器,TimeClientHandler不再使用ByteBuf:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    UnixTime m = (UnixTime) msg;
    System.out.println(m);
    ctx.close();
}

怎麼樣?看起更簡單和優雅,對吧? 相同地也可以應用在服務器端。現在我們首先更新TimeServerHandler中的代碼:

@Override
public void channelActive(ChannelHandlerContext ctx) {
    ChannelFuture f = ctx.writeAndFlush(new UnixTime());
    f.addListener(ChannelFutureListener.CLOSE);
}

現在,唯一缺少的是一個編碼器,它是一個ChannelOutboundHandler的實現,是將UnixTime轉換回ByteBuf。 它比編寫解碼器簡單得多,因爲在編碼消息時不需要處理數據包分段和組合。

package com.yiibai.netty.timepojo;

public class TimeEncoder extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        UnixTime m = (UnixTime) msg;
        ByteBuf encoded = ctx.alloc().buffer(4);
        encoded.writeInt((int)m.value());
        ctx.write(encoded, promise); // (1)
    }
}
  1. 在這一行有很多重要的東西。
    首先,我們按原樣傳遞原始的ChannelPromise,以便Netty將編碼數據實際寫入時將其標記爲成功或失敗。
    第二步,我們沒有調用ctx.flush()。 有一個單獨的處理程序方法void flush(ChannelHandlerContext ctx),它用於覆蓋flush()操作。

要進一步簡化,可以使用MessageToByteEncoder

public class TimeEncoder extends MessageToByteEncoder<UnixTime> {
    @Override
    protected void encode(ChannelHandlerContext ctx, UnixTime msg, ByteBuf out) {
        out.writeInt((int)msg.value());
    }
}

剩下的最後一個任務是在TimeServerHandler之前將TimeEncoder插入到服務器端的ChannelPipeline中,這裏將留作一個簡單的練習吧。

參考代碼: Netty->NettyTutorial->com.yiibai.netty.pojo;