This is the 9th day of my participation in the August More Text Challenge. For details, see:August is more challenging

Introduction to the

As mentioned in the previous article, for NioSocketChannel, it does not receive the most basic string messages, only ByteBuf and FileRegion. However, ByteBuf is processed in binary form, which is too unintuitive and cumbersome for programmers to handle. Is it possible to handle simple Java objects directly? This article will explore this issue.

Decode and encode

For example, if we want to write a string directly into a channel, we know from the previous article 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))
Copy the code

ChannelPromise only accepts ByteBuf and FileRegion. How do you do that?

Since ChannelPromise only accepts ByteBuf and FileRegion, we need to convert strings to ByteBuf.

That is, convert the String to ByteBuf before you write the String, and convert the ByteBuf to String when you read the data.

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

Then our idea came out. Add encode in ChannelPipeline, which is used to encode data into ByteBuf when data is written, and then add decode, which is used to decode data into corresponding objects when data is written.

Encode,decode, are you familiar with that? That’s the serialization of the object.

Object serialization

Object serialization in Netty is to convert objects and ByteBuf directly to each other. Of course, we can implement this transformation ourselves. But Netty already provides us with two convenient conversion classes: ObjectEncoder and ObjectDecoder.

Let’s start with ObjectEncoder, which turns objects into ByteBuf.

This class is 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); }}Copy the code

ObjectEncoder inherited MessageToByteEncoder, and MessageToByteEncoder inherited ChannelOutboundHandlerAdapter again. Why OutBound? This is because we are translating the object to which we are writing, so it is outbound.

We first encapsulate an out ByteBuf with a ByteBufOutputStream. In the bout, we first write a LENGTH_PLACEHOLDER field that represents the length of the Byte in the stream. And then use a CompactObjectOutputStream to encapsulate bout, finally can use CompactObjectOutputStream written to the object.

Corresponding, netty and a ObjectDecoder object, used to convert ByteBuf into the corresponding objects, ObjectDecoder inherited from LengthFieldBasedFrameDecoder, In fact, he is a ByteToMessageDecoder, is also a ChannelInboundHandlerAdapter, used to deal with data read.

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

As you can see from the code above, you can read the object directly by converting the input ByteBuf to a ByteBufInputStream and finally to CompactObjectInputStream.

Use encoders and decoders

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

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

// Define bossGroup and 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.a ddLast (add / / encoder and decoder new ObjectEncoder (), new ObjectDecoder (ClassResolvers. CacheDisabled (null)), new PojoServerHandler()); }}); B ind(PORT).sync().channel().closeFuture().sync();Copy the code

Similarly, for the client, 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.a ddLast (add / / encoder and decoder new ObjectEncoder (), new ObjectDecoder (ClassResolvers. CacheDisabled (null)), new PojoClientHandler()); }}); B.connector (HOST, PORT).sync().channel().closeFuture().sync();Copy the code

Add ObjectEncoder and ObjectDecoder to ChannelPipeline.

Finally, it can be used on the client and browser side by calling:

Ctx. write(" Go! );Copy the code

I’m just writing to the string object.

conclusion

With ObjectEncoder and ObjectDecoder, we are not constrained by ByteBuf, and the flexibility of the program is greatly improved.

An example for this article is learn-Netty4

This article is available at www.flydean.com/08-netty-po…

The most popular interpretation, the most profound dry goods, the most concise tutorial, many you do not know the small skills waiting for you to find!

Welcome to follow my public number: “procedures those things”, understand technology, more understand you!