Netty is mainly used for network communication, and network communication involves the transmission of data, which cannot be directly transmitted and requires a series of processing. Java serialization is one of the processing methods, but due to a variety of shortcomings, generally not used, here we introduce a more excellent coding and decoding technology MessagePack.

This article is the third in my “Getting Netty” series and builds on the previous two. We use Springboot integrated Netty.

Why not use Java serialization

Serialization without Java generally boils down to the following.

1. Inability to cross languages

For example, Java encoded data, C++ does not recognize, nor can decode.

2, low performance

Encoding and decoding speed is too slow.

3. The bit flow is too large

After encoding, a lot of other data is added, taking up space.

4, the development is difficult

Not developer friendly.

Any one of the above is a huge disadvantage, so we will introduce one of the encoding and decoding techniques here, called MessagePack. Why use this? Because all four of the above work well, use it. Of course, there are many other excellent technologies like Protobuf, which will be used later. My current project is C++ and Java communication, so I chose this framework.

Since MessagePack is so good, let’s go straight to using it in Netty.

Ii. MessagePack codec

1, the premise

As mentioned earlier, Springboot is used for development, so we’ll add the dependencies first, or if you don’t use Maven to download the jars directly.

In addition, this example code already works in Idea, and you’d better build two Moducle runs, one client and one server.

       <! -- Add netty dependency -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0. 1</version>
        </dependency>
        <dependency>
            <groupId>org.msgpack</groupId>
            <artifactId>msgpack</artifactId>
            <version>0.6.12</version>
        </dependency>
Copy the code

With the JAR package imported, let’s take a look at the client and server code.

The client and server implement the simple function of sending 10 Student object data to the server when establishing a connection.

2. Server-side code

(1) Define poJO object Student class

@Message
public class Student implements Serializable {
    private String name;
    private Integer age;
	Getters and setters
    @Override
    public String toString(a) {
        return "Student{" +
                "name='" + name + '\' ' +
                ", age=" + age +
                '} '; }}Copy the code

There are two things to note here:

Point one: Add the @message annotation to indicate that this can be serialized.

Second point: Be sure to have a default constructor. It was reportedly fixed in version 0.7, but it looks a bit cumbersome in form. And when you use version 0.7, the @message annotation is no longer there. It’s a hassle. It’s better to remember these two, with annotations and default constructors.

(2) MsgPackEncoder

/ / encoder
public class MsgPackEncoder extends MessageToByteEncoder<Object> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
        MessagePack messagePack = new MessagePack();
        byte[] raw = messagePack.write(o); byteBuf.writeBytes(raw); }}Copy the code

Create a MessagePack object, convert object O to byte and store it in ByteBuf.

(3) MsgPackDecoder

/ / decoder
public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception {
        int length = byteBuf.readableBytes();
        byte[] array = new byte[length];
        byteBuf.getBytes(byteBuf.readerIndex(), array, 0, length);
        MessagePack messagePack = newMessagePack(); list.add(messagePack.read(array)); }}Copy the code

Here we inherit the MessageToMessageDecoder, and then rewrite the decode method, inside the MessagePack and then convert the buffer byte into an object.

(4) Server-side Server class

@Component
public class NettyServer {
    EventLoopGroup boss = new NioEventLoopGroup();
    EventLoopGroup work = new NioEventLoopGroup();
    @PostConstruct
    public void start(a) throws InterruptedException {
        try {
            final ServerBootstrap b = new ServerBootstrap();
            b.group(boss, work)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 1024)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) throws Exception {
                   ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535.0.2.0.2));
                   ch.pipeline().addLast(new MsgPackDecoder());
                   ch.pipeline().addLast(new LengthFieldPrepender(2));
                   ch.pipeline().addLast(new MsgPackEncoder());
                   ch.pipeline().addLast(newServerUAVHandler()); }}); ChannelFuture f = b.bind(8883).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
        } finally{ boss.shutdownGracefully(); work.shutdownGracefully(); }}}Copy the code

It’s basically all routine code, you can just use it, and the core of it is the try block. We are most concerned about the following section:

 b.group(boss, work)
  .channel(NioServerSocketChannel.class)
  .option(ChannelOption.SO_BACKLOG, 1024)
  .childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     protected void initChannel(SocketChannel ch) throws Exception {
         ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535.0.2.0.2));
         ch.pipeline().addLast(new MsgPackDecoder());
         ch.pipeline().addLast(new LengthFieldPrepender(2));
         ch.pipeline().addLast(new MsgPackEncoder());
         ch.pipeline().addLast(newServerUAVHandler()); }});Copy the code

Let’s examine the code:

The first class: LengthFieldBasedFrameDecoder

Parameter (1) maxFrameLength: indicates the maximum length of the package, beyond which special treatment will be done.

Parameter (2) lengthFieldOffset: specifies the offset of the specified length field, indicating that the length field is skipped by a specified length of bytes.

Parameter (3) lengthFieldLength: The length of this data frame;

Parameter (4) lengthAdjustment: this field plus the length field is equal to the length of the data frame, and the size of the package lengthAdjustment.

Parameter (5) InitialbytestStrip: Testtestis displayed when a complete data packet is obtained, the specified bits and bytes are ignored.

Second class: MsgPackDecoder

This class is the decoder class that we changed to create

The third class: LengthFieldPrepender

Clients use LengthFieldPrepender to add the data packet Length field, the receiving party use LengthFieldBasedFrameDecoder decoding.

The fourth class: MsgPackEncoder

This is the encoder class we just created.

The fifth class: ServerUAVHandler

This is our business processing class, where we handle various events on the client side.

Five classes can be seen from the above LengthFieldBasedFrameDecoder and LengthFieldPrepender, can automatically block TCP at the bottom of the unpacking and sticky package problem, so here, in order to solve the problem of glue bag and unpacking.

Let’s take a look at our ServerUAVHandler class, focusing on our business processing class

(5) Handler class

public class ServerUAVHandler extends ChannelHandlerAdapter {
    private int counter=0;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("channelRead");
        List<Object> students = (List<Object>) msg;
        for (Object student : students) {
            System.out.println("Attribute:" + student);
        }
        System.out.println("counter:"+ ++counter);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
    													throws Exception { ctx.close(); }}Copy the code

The channelActive method handles connection requests from clients, and the channelRead method reads data from clients. We used the counter variable to log a few pieces of data sent by the client, but there was a pit we needed to be aware of.

Note: List<> must be the Object class, not our Student.

Now that our server is almost done, it should look like this:

Next you can look at the client side.

3. Client code

(1) Define poJO object Student class: same as server Student class

(2) MsgPackEncoder: same as server side

(3) MsgPackDecoder: the same as the server side

(4) Client Client class

@Component
public class NettyClient {
    EventLoopGroup group = new NioEventLoopGroup();
    @PostConstruct
    public void start(a) throws Exception {
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(
                                new LengthFieldBasedFrameDecoder(65535.0.2.0.2));
                            ch.pipeline().addLast(new MsgPackDecoder());
                            ch.pipeline().addLast(new LengthFieldPrepender(2));
                            ch.pipeline().addLast(new MsgPackEncoder());
                            ch.pipeline().addLast(newClientHandler()); }}); ChannelFuture channelFuture = b.connect("127.0.0.1".8883).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{ group.shutdownGracefully(); }}}Copy the code

As with the server, we’ve already analyzed it, so let’s take a look at the different clientHandler class implementations.

public class ClientHandler extends ChannelHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) 
    										throws Exception {
        Student loopStudent;
        for (int i = 1; i <= 10; i++) {
            loopStudent = new Student();
            loopStudent.setName("Feng Dongdong"+i); loopStudent.setAge(i); ctx.writeAndFlush(loopStudent); }}}Copy the code

In this case, the client sends object data when it establishes a connection with the server. The server must also receive 10. When the client is finished, it should look like this:

4. Experimental verification

Now run the server first and then the client to see the output. The server is shown here.

We see that the server receives 10 calls without sticky packets.

The performance of this MessagePack approach is not optimal when compared to other frameworks, but it is much better compared to Java serialization mechanism. I wrote a protobuf article before, if you are interested, you can check it out on my home page.

Now we can not only solve the problem of sticking and unpacking, but also deal with the problem of encoding and decoding, a new problem comes out, the server to the client to actively push messages how to do? At this time the traditional HTTP protocol, obviously can not meet our needs. There are too many shortcomings. A new technology was born, called WebSocket, which will be covered in the next article. Thank you for your support.