netty encoding and decoding and packet sticking and unpacking processing
Codec
When you send or receive a message through Netty, a data conversion will occur. The inbound message will be decoded: from byte to another format (such as java object); If it is an outbound message, it will be encoded into bytes.
Netty provides a series of practical codecs. They all implement the ChannelInboundHadnler or ChannelOutboundHandler interface. In these classes, the channelRead method has been overridden. Taking inbound as an example, this method will be called for each message read from inbound Channel. Then, it will call the decode() method provided by the known decoder to decode and forward the decoded bytes to the next ChannelInboundHandler in the ChannelPipeline.
If you want to achieve efficient encoding and decoding, you can use protobuf, but protobuf needs to maintain a large number of proto files, which is more troublesome. Now you can generally use protobuf.
Protopuff is a serialization method based on protopuff implementation. Its most obvious advantage over protopuff is that it doesn't need us to write without losing performance proto file to achieve serialization.
Sticking and unpacking
TCP is a streaming protocol, which is a long string of binary data without boundaries. As a transport layer protocol, TCP does not know the specific meaning of the upper layer service data. It will divide the data packets according to the actual situation of the TCP buffer. Therefore, it is considered as a complete packet in terms of service. It may be split into multiple packets for transmission by TCP, or multiple small packets may be encapsulated into one large packet for transmission, This is the so-called TCP packet sticking and unpacking problem. Flow oriented communication has no message protection boundary.
<!-- introduce netty package--> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.35.Final</version> </dependency> <!--use protostuff Implement serialization--> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-api</artifactId> <version>1.0.10</version> </dependency> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.0.10</version> </dependency> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.0.10</version> </dependency>
Use the LengthFieldBasedFrameDecoder as the unpacker
For details, please refer to this article netty source code analysis of LengthFieldBasedFrameDecoder
Unpacker
public class PackageSplitUtil extends LengthFieldBasedFrameDecoder { public PackageSplitUtil() { super(Integer.MAX_VALUE, Constant.LENGTH_OFFECT, Constant.LENGTH_BYTES_COUNT, 0, Constant.LENGTH_BYTES_COUNT); } @Override protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception{ return super.decode(ctx, in); } }
encoder
public class MyNettyEncoder extends MessageToByteEncoder<NettyMessage> { @Override protected void encode(ChannelHandlerContext channelHandlerContext, NettyMessage msg, ByteBuf out) throws Exception { byte[] bytes = ProtostuffUtil.serializer(msg); // Gets the length of the message int length = bytes.length; ByteBuf buf = Unpooled.buffer(Constant.HEAD_LEAGTH + length); // Write protocol version buf.writeShort(Constant.PROTOCOL_VERSION); // Write message length buf.writeShort(length); // Write message body buf.writeBytes(bytes); out.writeBytes(buf); } }
decoder
public class MyNettyDecoder extends ByteToMessageDecoder { private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class); @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { // Read the length of the transmitted message. int length = in.readUnsignedShort(); // If the length is less than 0, the data is illegal and the connection is closed if (length < 0) { ctx.close(); } // If the length of the read message body is less than the length of the transmitted message if (length > in.readableBytes()) { log.info("The read message length is less than the transmitted message length!"); return; } ByteBuf frame = Unpooled.buffer(length); in.readBytes(frame); try { byte[] inByte = frame.array(); // Byte to object NettyMessage msg = ProtostuffUtil.deserializer(inByte, NettyMessage.class); if (msg != null) { // Get business message header out.add(msg); } } catch (Exception e) { log.error("====" + ctx.channel().remoteAddress() + ",decode failed===="); } } }
Server use
public class NettyServer { private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class); private static Channel serverChannel; private static final NettyServer nettyServer = new NettyServer(); public static NettyServer getInstance(){ return nettyServer; } private NettyServer(){ start(); } public static void closeServer(){ if (nettyServer != null){ serverChannel.close(); serverChannel = null; } } private static void start(){ //Create a thread to process connection requests EventLoopGroup bossGroup = new NioEventLoopGroup(1); //Create threads to interact with clients EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //Create the startup object of the server ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer<SocketChannel>() {//Create a channel initialization object and set initialization parameters @Override protected void initChannel(SocketChannel ch) throws Exception { log.info("[ client ]" + ch.remoteAddress() + " Connected"); //Set the processor for the socketChannel of the workGroup ChannelPipeline pipeline = ch.pipeline(); //heartbeat mechanism pipeline.addLast(new IdleStateHandler(Constant.HEART_TIMEOUT, 0, 0, TimeUnit.SECONDS)); pipeline.addLast(new PackageSplitUtil()); //decoder pipeline.addLast(new MyNettyEncoder()); //encoder pipeline.addLast(new MyNettyDecoder()); //Custom processor pipeline.addLast(new NettyServerHandler()); } }); ChannelFuture channelFuture = bootstrap.bind(Constant.NETTY_SERVER_PORT).sync(); serverChannel = channelFuture.channel(); log.info("====netty The server has started successfully==== Port:" + Constant.NETTY_SERVER_PORT); //Monitor the channel closing. closeFuture is an asynchronous operation. Monitor the channel closing serverChannel.closeFuture().addListener((ChannelFutureListener) future -> { log.info("====Gracefully closed EventLoopGroup===="); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }); } catch (InterruptedException e) { e.printStackTrace(); }finally { } } }
Constant partial parameters
/** * Agreed header length */ public static int HEAD_LEAGTH = 2; /** * Agreed version number */ public static int PROTOCOL_VERSION = 1; /** * Agreed length offset (2 bytes) */ public static int LENGTH_OFFECT = 2; /** * Agreed length bytes (short type) */ public static int LENGTH_BYTES_COUNT = 2; /** * port */ public static int NETTY_SERVER_PORT = 9000 ;