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:
- 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
- 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:
- Not all ByteBuf operations are supported. If an unsupported method is called, an unsupportationoperationexception will be thrown, such as replayingdecoderbytebufාarray() method
- 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
- 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
- DelimiterBasedFrameDecoder: use custom special characters as the message separator
- HttpObjectDecoder: decoder of Http data
- LengthFieldBasedFrameDecoder: identify the whole package information by specifying the length, so that the problem of package sticking and half package can be handled automatically
- ObjectEncoder: encoder with object
- ...