简介

在之前的文章中咱们提到了,对于NioSocketChannel来说,它不接管最根本的string音讯,只接管ByteBuf和FileRegion。然而ByteBuf是以二进制的模式进行解决的,对于程序员来说太不直观了,解决起来也比拟麻烦,有没有可能间接解决java简略对象呢?本文将会探讨一下这个问题。

decode和encode

比方咱们须要间接向channel中写入一个字符串,在之前的文章中,咱们晓得这是不能够的,会报上面的谬误:

DefaultChannelPromise@57f5c075(failure: java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion))

也就说ChannelPromise只承受ByteBuf和FileRegion,那么怎么做呢?

既然ChannelPromise只承受ByteBuf和FileRegion,那么咱们就须要把String对象转换成ByteBuf即可。

也就是说在写入String之前把String转换成ByteBuf,当要读取数据的时候,再把ByteBuf转换成String。

咱们晓得ChannelPipeline中能够增加多个handler,并且管制这些handler的程序。

那么咱们的思路就进去了,在ChannelPipeline中增加一个encode,用于数据写入的是对数据进行编码成ByteBuf,而后再增加一个decode,用于在数据写出的时候对数据进行解码成对应的对象。

encode,decode是不是很相熟?对了,这就是对象的序列化。

对象序列化

netty中对象序列化是要把传输的对象和ByteBuf间接相互转换,当然咱们能够本人实现这个转换对象。然而netty曾经为咱们提供了不便的两个转换类:ObjectEncoder和ObjectDecoder。

先看ObjectEncoder,他的作用就是将对象转换成为ByteBuf。

这个类很简略,咱们对其剖析一下:

public class ObjectEncoder extends MessageToByteEncoder<Serializable> {    private static final byte[] LENGTH_PLACEHOLDER = new byte[4];    @Override    protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {        int startIdx = out.writerIndex();        ByteBufOutputStream bout = new ByteBufOutputStream(out);        ObjectOutputStream oout = null;        try {            bout.write(LENGTH_PLACEHOLDER);            oout = new CompactObjectOutputStream(bout);            oout.writeObject(msg);            oout.flush();        } finally {            if (oout != null) {                oout.close();            } else {                bout.close();            }        }        int endIdx = out.writerIndex();        out.setInt(startIdx, endIdx - startIdx - 4);    }}

ObjectEncoder继承了MessageToByteEncoder,而MessageToByteEncoder又继承了ChannelOutboundHandlerAdapter。为什么是OutBound呢?这是因为咱们是要对写入的对象进行转换,所以是outbound。

首先应用ByteBufOutputStream对out ByteBuf进行封装,在bout中,首先写入了一个LENGTH_PLACEHOLDER字段,用来示意stream中中Byte的长度。而后用一个CompactObjectOutputStream对bout进行封装,最初就能够用CompactObjectOutputStream写入对象了。

对应的,netty还有一个ObjectDecoder对象,用于将ByteBuf转换成对应的对象,ObjectDecoder继承自LengthFieldBasedFrameDecoder,实际上他是一个ByteToMessageDecoder,也是一个ChannelInboundHandlerAdapter,用来对数据读取进行解决。

咱们看下ObjectDecoder中最重要的decode办法:

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {        ByteBuf frame = (ByteBuf) super.decode(ctx, in);        if (frame == null) {            return null;        }        ObjectInputStream ois = new CompactObjectInputStream(new ByteBufInputStream(frame, true), classResolver);        try {            return ois.readObject();        } finally {            ois.close();        }    }

下面的代码能够看到,将输出的ByteBuf转换为ByteBufInputStream,最初转换成为CompactObjectInputStream,就能够间接读取对象了。

应用编码和解码器

有了下面两个编码解码器,间接须要将其增加到client和server端的ChannelPipeline中就能够了。

对于server端,其外围代码如下:

//定义bossGroup和workerGroup        EventLoopGroup bossGroup = new NioEventLoopGroup(1);        EventLoopGroup workerGroup = new NioEventLoopGroup();        try {            ServerBootstrap b = new ServerBootstrap();            b.group(bossGroup, workerGroup)             .channel(NioServerSocketChannel.class)             .handler(new LoggingHandler(LogLevel.INFO))             .childHandler(new ChannelInitializer<SocketChannel>() {                @Override                public void initChannel(SocketChannel ch) throws Exception {                    ChannelPipeline p = ch.pipeline();                    p.addLast(                            // 增加encoder和decoder                            new ObjectEncoder(),                            new ObjectDecoder(ClassResolvers.cacheDisabled(null)),                            new PojoServerHandler());                }             });            // 绑定端口,并筹备承受连贯            b.bind(PORT).sync().channel().closeFuture().sync();

同样的,对于client端,咱们其外围代码如下:

EventLoopGroup group = new NioEventLoopGroup();        try {            Bootstrap b = new Bootstrap();            b.group(group)             .channel(NioSocketChannel.class)             .handler(new ChannelInitializer<SocketChannel>() {                @Override                public void initChannel(SocketChannel ch) throws Exception {                    ChannelPipeline p = ch.pipeline();                    p.addLast(                            // 增加encoder和decoder                            new ObjectEncoder(),                            new ObjectDecoder(ClassResolvers.cacheDisabled(null)),                            new PojoClientHandler());                }             });            // 建设连贯            b.connect(HOST, PORT).sync().channel().closeFuture().sync();

能够看到下面的逻辑就是将ObjectEncoder和ObjectDecoder增加到ChannelPipeline中即可。

最初,就能够在客户端和浏览器端通过调用:

ctx.write("加油!");

间接写入字符串对象了。

总结

有了ObjectEncoder和ObjectDecoder,咱们就能够不必受限于ByteBuf了,程序的灵便水平失去了大幅晋升。

本文的例子能够参考:learn-netty4

本文已收录于 http://www.flydean.com/08-netty-pojo-buf/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」,懂技术,更懂你!