Netty uses a length decoder to implement half-packet sticky packet processing

I. Overview:

  1. Noun explanation

  Half packet: TCP protocol, there is a buffer, if a packet sent is larger than the size of the buffer, there will be a half packet phenomenon, that is, a packet is divided into two transmissions.

  Sticky packets: It is also because of the existence of the buffer area. If many small-sized packets are sent in a short time, the bottom layer of the TCP protocol implementation will send these small packets together.

  2. Overview

  First of all, the appearance of the semi-packet sticky packet is not due to the network framework, but a problem arising from the implementation of the TCP protocol. How to solve the half-packet and sticky packet, there are mainly the following methods in the netty framework we use.

  1) Set the end delimiter of the packet

  2) The fixed-length packet is sent, and the fixed-length packet is decoded to remove the spaces when in use

  3) Add a mark bit to the head, and remove the mark bit when using it.

  3. Common built-in decoders

// (1) Use FixedLengthFrameDecoder to solve the problem of sticky packets of fixed-length messages;
//
// (2) Use LineBasedFrameDecoder and StringDecoder to solve the problem of TCP sticky packets with carriage return and linefeed as the end of the message;
//
// (3) The DelimiterBasedFrameDecoder special delimiter decoder is used to solve the TCP sticky packet problem with special symbols as the end of the message; (It is a custom way of 2)
//
// (4) The last one is also this article Used, through the LengthFieldBasedFrameDecoder custom length decoder to solve the TCP sticky packet problem.

 

Two, encoding and decoding

1. Encoding and decoding process

The custom handler of the client sends the information, after the processing of LengthProtocoltEncode (the position is the second to last), adds the protocol name and length field, and sends it to the server. The server passes through the decoder inherited from LengthFieldBasedFrameDecoder (the position is the first ) Automatic processing, handed over to the length field decoder LenghtProtocoltDecode processing (position is the second), and finally handed over to the last processor on the server side to print the client input, and Echo returns the data.

Note: If the encoder position is input, then the input is processed from front to back. Therefore, after the length is decoded, the length is removed, and the output is in the opposite direction. Therefore, the second to last one must process the real output content. (In addition: if you add a string encoder, it must be placed behind the custom processor, because the custom processor writes data and writes String, which needs to be processed by the string encoder)

2. Agreement rules

  [2 length protocol version, 2 length length field, real packet content]

3. Three main encoding and decoding processors

public class AllHandlers {

	
	/**
	 *
	 * 继承LengthFieldBasedFrameDecoder的解码器,主要是传入自定义的参数。
	 */
	public static class LengthPackageSpliter extends LengthFieldBasedFrameDecoder {
		
		/**
		 * 参数[1] : 长度字段偏移,参数[2] : 长度字段占用尺寸
		 */
		public LengthPackageSpliter() {
			super(Integer.MAX_VALUE, Constants.LENGTH_OFFSET, Constants.LENGTH_BYTES_COUNT);
		}

		@Override
		protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
			return super.decode(ctx, in);
		}
	}
	
	
	/**
	 * 消息编码器,用于发送消息后,对消息内容自动增加协议字段、长度字段。
	 */
	public static class LengthProtocoltEncode extends MessageToByteEncoder<ByteBuf> {
		
		@Override
		protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
			int length = msg.readableBytes();
			ByteBuf buff = Unpooled.buffer(Constants.PROTOCOL_HEADLENGTH + length);
			buff.writeShort(Constants.PROTOCOL_VERSION);
			buff.writeShort(length);
			buff.writeBytes(msg);
			out.writeBytes(buff);
		}
		
	}

//  消息解码器:方式1
//	public static class LenghtProtocoltDecode extends ChannelInboundHandlerAdapter {
//
//		@Override
//		public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//			// 去除长度字符串
//			ByteBuf buff = (ByteBuf) msg;
//			short vs = buff.readShort();
//			short length = buff.readShort();
//			ByteBuf buf = buff.readBytes(length);
//			super.channelRead(ctx, buf);
//		}
//	}

//  消息解码器:方式2
	public static class LenghtProtocoltDecode extends ByteToMessageDecoder {

		@Override
		protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
			short vs = in.readShort();
			short length = in.readShort();
			ByteBuf buf = in.readBytes(length);
			out.add(buf);
		}
	}

 Three, code implementation

1. Server

public class PkgServer {
	
	public static void main(String[] args) {
		new PkgServer().bind(7000);
	}
	
	public void bind(int port) {
		EventLoopGroup bossGroup = new NioEventLoopGroup(1);
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
					.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new LengthPackageSpliter());
							ch.pipeline().addLast(new LenghtProtocoltDecode());
							ch.pipeline().addLast(new LengthProtocoltEncode());
							ch.pipeline().addLast(new ServerHandler());
						}
					});
			ChannelFuture future = b.bind(port).sync();
			System.out.println("server is start!");
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}
	
	public class ServerHandler extends ChannelInboundHandlerAdapter {

		@Override
		public void channelActive(ChannelHandlerContext ctx) throws Exception {
			SocketChannel channel = (SocketChannel) ctx.channel();
			System.out.println("链接报告信息:有一客户端链接到本服务端");
			System.out.println("链接报告IP:" + channel.localAddress().getHostString());
			System.out.println("链接报告Port:" + channel.localAddress().getPort());
			System.out.println("链接报告完毕");
		}

		@Override
		public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
			ByteBuf buff = (ByteBuf) msg;
			byte[] readdata = new byte[buff.readableBytes()];
			buff.readBytes(readdata);
			String result = new String(readdata);
			System.out.println("server receive msg:" +result);
			String response = "echo" + result;
			buff = Unpooled.buffer(response.getBytes().length);
			buff.writeBytes(response.getBytes());
			ctx.writeAndFlush(buff);
		}
	}
	
}


2. Client

public class PkgClient {

	public static void main(String[] args) throws Exception {
		PkgClient client = new PkgClient();
		client.bind("127.0.0.1", 7000);
	}
	
	public void bind(String address, int port) throws Exception {
		EventLoopGroup loopGroup = new NioEventLoopGroup();
		try {
			Bootstrap b = new Bootstrap();
			b.group(loopGroup).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
					.handler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel channel) throws Exception {
							channel.pipeline().addLast(new LengthPackageSpliter());
							channel.pipeline().addLast(new LenghtProtocoltDecode());
							channel.pipeline().addLast(new LengthProtocoltEncode());
							channel.pipeline().addLast(new MyClientHandler());
						}
					});
			// 启用客户端连接
			ChannelFuture future = b.connect(address, port).sync();
			future.channel().closeFuture().sync();
		} finally {
			loopGroup.shutdownGracefully();
		}
	}
	
	public static class MyClientHandler extends ChannelInboundHandlerAdapter {

		@Override
		public void channelActive(ChannelHandlerContext ctx) throws Exception {
			byte[] data = ("this is one line message.".getBytes());
			ByteBuf buff = Unpooled.buffer(data.length);
			buff.writeBytes(data);
			ctx.writeAndFlush(buff);
		}

		@Override
		public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
			ByteBuf buf = (ByteBuf)msg;
			byte[] readdata = new byte[buf.readableBytes()];
			buf.readBytes(readdata);
			System.out.println("client receive:" + new String(readdata));
		}
	}
	

}

 

3. Add String type encoding and decoding

In the above case, we use ByteBuf for both sending and receiving. We modified it to use String type for sending and String type for receiving. This requires two encoders, StringDecoder and StringEncoder, which are two encodings.

Where is the processor placed in the processor chain (pipeline)?

First of all, StringDecoder is a message input, which converts ByteBuf to String, which should be placed between the last in type processor and the custom processor, that is, the third position.

The StringEncoder is to receive the String input of the custom processor and convert it to the ByteBuf type. Then it should be placed in a position before the custom processor. The total processor chain should be in the following order.

	ch.pipeline().addLast(new LengthPackageSpliter());
							ch.pipeline().addLast(new LenghtProtocoltDecode());
							ch.pipeline().addLast(new StringDecoder()); // 增加String解码
							ch.pipeline().addLast(new LengthProtocoltEncode());
							ch.pipeline().addLast(new StringEncoder()); // 增加String编码
							ch.pipeline().addLast(new MyClientHandler());

Then we will correspondingly transform the places where the data is input and read, as follows:

public static class MyClientHandler extends ChannelInboundHandlerAdapter {

		@Override
		public void channelActive(ChannelHandlerContext ctx) throws Exception {
//			byte[] data = ("this is one line message.".getBytes());
//			ByteBuf buff = Unpooled.buffer(data.length);
//			buff.writeBytes(data);
//			ctx.writeAndFlush(buff);
			ctx.writeAndFlush("this is one line message");
		}

		@Override
		public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//			ByteBuf buf = (ByteBuf)msg;
//			byte[] readdata = new byte[buf.readableBytes()];
//			buf.readBytes(readdata);
//			System.out.println("client receive:" + new String(readdata));
			System.out.println("client receive:" +msg );
		}
	}

The server modification is similar and omitted

 

 

end!!!

 

 

 

 

 

Guess you like

Origin blog.csdn.net/shuixiou1/article/details/114901198