[Netty learning notes] X. use of codec

Keywords: Netty codec socket Java

Basic explanation

Component design of Netty: the main components of Netty are Channel, EventLoop, ChannelFuture, ChannelHandler, ChannelPipe, etc.

ChannelHandler acts as a container for application logic that processes inbound and outbound data. For example, to implement the ChannelInboundHandler interface (or ChannelInboundHandler adapter), you can receive inbound events and data, which will be processed by the business logic. Business logic is usually written in one or more channel inboundhandlers. The principle of ChannleOutboundHandler is the same, but it is used to process outbound data.

ChannelPipeline provides a container for the channelhandler chain. Take the client application as an example. If the movement direction of events is from the client to the server, we call these events outbound, that is, the data sent by the client to the server will be processed by a series of channeloutboundhandlers in the pipeline, otherwise, if the movement direction of events is from the server to the client, it is called inbound.

Codec

When Netty sends or receives a message, a data conversion occurs. The inbound message will be decoded from the binary byte to the object format; if it is the outbound message, the business data will be encoded as the binary byte stream.

Netty provides a series of practical codecs that implement the ChannelInboundHandler or ChannelOutboundHandler interface. Among the implementation classes of these codecs, the channelRead method is generally rewritten to encode and decode.

For example, the channelRead method will be called first, then it will call the decode() method provided by the decoder to decode, and forward the decoded bytes to the next ChannelInboundHandler in ChannelPipeline.

Decoder interface ByteToMessageDecoder, encoder interface messagetobyencoder, or direct implementation of MessageToMessageCodec includes encoding and decoding. When you need to customize the codec, you only need to implement the interface.

Custom decoder

Example:

public class ToIntegerDecoder extends ByteToMessageDecoder{
  
  @Override
  protected void decode(ChannleHandlerContext ctx,ByteBuf in,List<Object> out) throws Exception{
    if(in.readableBytes()>=4){
      out.add(in.readInt());
    }
  }
}

Explain:

Each time an inbound reads 4 bytes from ByteBuf (because int is 4 bytes), decodes it into an int, and adds it to the next list. When no more elements can be added to the list, the contents of the list will be sent to the next ChannleInboundHandler. In this case, it is necessary to determine whether the number of bytes is > = 4, or the problem of package sticking and unpacking will occur

Here is an example to illustrate the calling order of handler chain in Netty

Example requirements:

  • Client sends long to server
  • The server sends long to the client

Server side code

package com.wojiushiwo.codec;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Created by myk
 * 2020/1/29 7:02 p.m.
 */
public class NettyCustomCodecServer {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {

                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("encoder",new LongEncoder());
                            pipeline.addLast("decoder",new LongDecoder());
                            pipeline.addLast(new CustomCodecServerHandler());
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind(8091).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

//handler
package com.wojiushiwo.codec;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * Created by myk
 * 2020/1/29 7:02 p.m.
 */
public class CustomCodecServerHandler extends SimpleChannelInboundHandler<Long> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
        System.out.println("Called CustomCodecServerHandler#channelRead0");
        System.out.println("from client:" + msg);

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Called CustomCodecServerHandler#channelReadComplete");
        ctx.writeAndFlush(123456789L);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Client code

package com.wojiushiwo.codec;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * Created by myk
 * 2020/1/29 7:02 p.m.
 */
public class NettyCustomCodecClient {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {

            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("encoder", new LongEncoder());
                            pipeline.addLast("decoder", new LongDecoder());
                            pipeline.addLast(new CustomCodecClientHandler());
                        }
                    });


            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8091).sync();
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}
//handler
package com.wojiushiwo.codec;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * Created by myk
 * 2020/1/29 7:02 p.m.
 */
public class CustomCodecClientHandler extends SimpleChannelInboundHandler<Long> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client CustomCodecClientHandler#channelActive");
        ctx.writeAndFlush(1999999L);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception {
        System.out.println("Client CustomCodecClientHandler#channelRead0");
        System.out.println("from server:" + msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

Custom codec code

//Encoder
package com.wojiushiwo.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * Created by myk
 * 2020/1/29 7:04 p.m.
 */
public class LongEncoder extends MessageToByteEncoder<Long> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
        System.out.println("Call encoder LongEncoder#encode");
        out.writeLong(msg);
    }

}

//Decoder
package com.wojiushiwo.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * Created by myk
 * 2020/1/29 7:03 p.m.
 */
public class LongDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        System.out.println("call LongDecoder#decode");
        //long 8 bytes
        if (in.readableBytes() >= 8) {
            out.add(in.readLong());
        }

    }
}

Server output:

Call longdecoder? Decode
Call to customcodecserverhandler × channelread0
from client:1999999
Customcodecserverhandler ා channelreadcomplete called
Call encoder longencoder × encode

Client output:

Customcodecclienthandler? Channelactive

Call encoder longencoder × encode
Call longdecoder? Decode
Client customcodecclienthandler × channelread0
from server:123456789

You can find the calling order by printing the string to the console:

Client-side channelactive = > client-side longencoder ᦇ encode = > server-side longdecoder ᦇ decode = > customcodecserverhandler ᦇ channelread0 = > customcodecserverhandler ᦇ channelreadcomplete = > server-side longencoder ᦇ encode = > client-side longdecoder ᦇ decode = > customcodecclienthandler ᦇ channelread0

Conclusion:

  1. Whether decoder handler or encoder handler, the received message type must be the same as the message type to be processed, otherwise the handler will not be executed
  2. When the decoder decodes the data, it needs to judge whether the data in the buffer is enough or the received result may be inconsistent with the expected result.
Decoder ReplayingDecoder

ReplayingDecoder inherits the ByteToMessageDecoder class. With this class, we don't need to explicitly determine whether the buffer is sufficient. This class will automatically do this. Generic S specifies the type of user state management, where Void indicates that state management is not required.

Example:

Refactoring LongDecoder with ReplayingDecoder

package com.wojiushiwo.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;

import java.util.List;

/**
 * Created by myk
 * 2020/1/29 7:47 p.m.
 */
public class LongDecoder2 extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
      //There is no need to judge whether the data is enough to read, and internal processing will judge
        out.add(in.readLong());
    }
}

Advantages and disadvantages of ReplayingDecoder:

Advantages: easy to use

Disadvantages:

  1. Not all ByteBuf operations are supported. If an unsupported method is called, an unsupportationoperationexception will be thrown, such as replayingdecoderbytebufාarray() method
  2. ReplayingDecoder will be slower than ByteToMessageDecoder in some cases. For example, when the network is slow and the message format is complex, the message will be split into multiple pieces and the speed will be slower.
Other codecs
  1. LineBasedFrameDecoder: this class is used inside Netty. It uses the end of line control character (\ n or \ r\n) as a separator to parse data
  2. DelimiterBasedFrameDecoder: use custom special characters as the message separator
  3. HttpObjectDecoder: decoder of Http data
  4. LengthFieldBasedFrameDecoder: identify the whole package information by specifying the length, so that the problem of package sticking and half package can be handled automatically
  5. ObjectEncoder: encoder with object
  6. ...
94 original articles published, 21 praised, 80000 visitors+
Private letter follow

Posted by itsjareds on Wed, 29 Jan 2020 20:16:37 -0800