Introduction

In the previous article, we mentioned that for NioSocketChannel, it does not receive the most basic string message, only ByteBuf and FileRegion. But ByteBuf is processed in binary form, which is too unintuitive for programmers, and it is troublesome to process. Is it possible to directly process simple java objects? This article will explore this issue.

decode and encode

For example, we need to write a string directly to the channel. In the previous article, we knew that this is not possible, and the following error will be reported:

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

In other words, ChannelPromise only accepts ByteBuf and FileRegion, so how to do it?

Since ChannelPromise only accepts ByteBuf and FileRegion, then we need to convert the String object to ByteBuf.

That is to say, convert String to ByteBuf before writing to String, and then convert ByteBuf to String when reading data.

We know that ChannelPipeline can add multiple handlers and control the order of these handlers.

Then our idea came out. Add an encode to ChannelPipeline, which is used for data writing to encode the data into ByteBuf, and then add a decode, which is used to decode the data into the corresponding when the data is written. Object.

Are encode and decode familiar? By the way, this is the serialization of objects.

Object serialization

Object serialization in netty is to directly convert the transmitted object and ByteBuf to each other. Of course, we can implement this conversion object ourselves. But netty has provided us with two convenient conversion classes: ObjectEncoder and ObjectDecoder.

First look at ObjectEncoder, his role is to convert objects into ByteBuf.

This class is very simple, let's analyze it:

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 inherits MessageToByteEncoder, and MessageToByteEncoder inherits ChannelOutboundHandlerAdapter. Why is OutBound? This is because we are going to convert the written object, so it is outbound.

First use ByteBufOutputStream to encapsulate out ByteBuf. In bout, first write a LENGTH_PLACEHOLDER field to indicate the length of Byte in the stream. Then use a CompactObjectOutputStream to encapsulate the bout, and finally you can use the CompactObjectOutputStream to write the object.

Correspondingly, netty also has an ObjectDecoder object, which is used to convert ByteBuf into a corresponding object. ObjectDecoder inherits from LengthFieldBasedFrameDecoder. In fact, it is a ByteToMessageDecoder and a ChannelInboundHandlerAdapter, which is used to process data reading.

Let's look at the most important decode method in ObjectDecoder:

    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();
        }
    }

As you can see in the above code, the input ByteBuf is converted to ByteBufInputStream, and finally converted to CompactObjectInputStream, and the object can be read directly.

Use encoders and decoders

With the above two codecs, you need to add them directly to the ChannelPipeline on the client and server sides.

For the server side, the core code is as follows:

//定义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();

Similarly, for the client side, our core code is as follows:

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();

You can see that the above logic is to add ObjectEncoder and ObjectDecoder to ChannelPipeline.

Finally, you can call on the client and browser:

ctx.write("加油!");

Write the string object directly.

Summarize

With ObjectEncoder and ObjectDecoder, we are not limited to ByteBuf, and the flexibility of the program has been greatly improved.

For the examples in this article, please refer to: learn-netty4

This article has been included in http://www.flydean.com/08-netty-pojo-buf/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", know technology, know you better!


flydean
890 声望437 粉丝

欢迎访问我的个人网站:www.flydean.com