Netty的ReplayingDecoder分析

说明

  • io.netty.handler.codec.ReplayingDecoder是io.netty.handler.codec.ByteToMessageDecoder的一个子类,是一个抽象类,它将字节流解码成其它的消息。
  • 需要ReplayingDecoder的子类实现decode(ChannelHandlerContext ctx, ByteBuf in, List out)这个函数,进行具体的解码。
  • ReplayingDecoder和ByteToMessageDecoder最大的不同是它允许子类实现函数decode() 和decodeLast()的时候,就好象需要的字节已经全部接收到一样,而不需要显式判断需要的字节是否已经全部接收到。
    例如,如果直接继承ByteToMessageDecoder类,decode() 函数的实现会类似下面的形式:
public class ServerBytesFramerDecoder  extends ByteToMessageDecoder {

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {	
		if (in.readableBytes() < 12) {
			return;
		}		
		out.add(in.readBytes(12));		
	}
}

而如果直接继承ReplayingDecoder类,decode() 函数的实现会类似下面的形式:

public class ServerBytesFramerDecoder  extends ReplayingDecoder<Void> {

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {			
		out.add(in.readBytes(12));		
	}
}
  • ReplayingDecoder之所以能做到上面那样,是因为ReplayingDecoder传递了一个特别的ByteBuf实现类ReplayingDecoderByteBuf:
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder {

    static final Signal REPLAY = Signal.valueOf(ReplayingDecoder.class, "REPLAY");

    private final ReplayingDecoderByteBuf replayable = new ReplayingDecoderByteBuf();
    private S state;
    private int checkpoint = -1;
final class ReplayingDecoderByteBuf extends ByteBuf {

    private static final Signal REPLAY = ReplayingDecoder.REPLAY;

    private ByteBuf buffer;
    private boolean terminated;
    private SwappedByteBuf swapped;
  • 当没有足够的字节的时候,ReplayingDecoderByteBuf会抛出一个Error类型,控制又回到ReplayingDecoder。这个Error类型是Signal:
public final class Signal extends Error implements Constant<Signal> {

    private static final long serialVersionUID = -221145131122459977L;
  • 当ReplayingDecoder捕获到Error后,会将buffer的readerIndex重置回初始的位置(也就是buffer的开始),当新的数据被接收到buffer以后,又会调用decode函数。
  • ReplayingDecoder尽管带来了方便,但也付出了一些代价。因为ReplayingDecoderByteBuf对ByteBuf的操作方法进行了覆盖,导致buffer的有些操作是禁止的。例如:
@Override
public byte[] array() {
    throw new UnsupportedOperationException();
}

@Override
public int arrayOffset() {
    throw new UnsupportedOperationException();
}

    @Override
    public boolean release(int decrement) {
        throw reject();
    }
    
    private static UnsupportedOperationException reject() {
        return new UnsupportedOperationException("not a replayable operation");
    }
  • 性能上可能有些降低。因为如果消息复杂的话,实现类的decode方法必须一次又一次地解析消息的同一部分(每次接收到新的字节,都会尝试解析,但接收到的字节数可能不够,导致解析失败,等待下一次再解析)。

示例

一个简单的正常示例

本示例的验证场景:
在这个示例中,客户端发送了12个字节的数据,服务端的ServerBytesFramerDecoder解析成功,传递给了后续的ServerRegisterRequestHandler。

服务端代码片段

package com.thb.power.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * 服务端的主函数
 * @author thb
 *
 */
public class MainStation {
	
	static final int PORT = Integer.parseInt(System.getProperty("port", "22335"));

	public static void main(String[] args) throws Exception {
		// 配置服务器
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		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 MainStationInitializer());
			
			// 启动服务端
			ChannelFuture f = b.bind(PORT).sync();
			
			// 等待直到server socket关闭
			f.channel().closeFuture().sync();
		} finally {
			// 关闭所有event loops以便终止所有的线程
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}

}
package com.thb.power.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class MainStationInitializer extends ChannelInitializer<SocketChannel> {

	 @Override
	 public void initChannel(SocketChannel ch) throws Exception {
		 ChannelPipeline p = ch.pipeline();		
		 
		 p.addLast(new LoggingHandler(LogLevel.INFO));
		 p.addLast(new ServerBytesFramerDecoder());	
		 p.addLast(new ServerRegisterRequestHandler());
	 }
}
package com.thb.power.server;

import java.util.List;

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

public class ServerBytesFramerDecoder  extends ReplayingDecoder<Void> {

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {				
		out.add(in.readBytes(12));		
	}
}
package com.thb.power.server;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ServerRegisterRequestHandler extends ChannelInboundHandlerAdapter {
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		ByteBuf m = (ByteBuf)msg;
		System.out.println("ServerRegisterRequestHandler: readableBytes: " + m.readableBytes());
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

启动服务端

在这里插入图片描述

启动客户端

在这里插入图片描述

从客户端发送12个字节的业务数据

在这里插入图片描述

观察服务端的输出

在这里插入图片描述
从上面服务端的输出可以看出,ServerRegisterRequestHandler收到了12个字节的数据。而ServerRegisterRequestHandler在ServerBytesFramerDecoder的后面,所以这12个字节的数据是ServerBytesFramerDecoder解析出来传递过来的。当时在ChannelPipeline添加的ChannelHandler的顺序:

 @Override
 public void initChannel(SocketChannel ch) throws Exception {
	 ChannelPipeline p = ch.pipeline();		
	 
	 p.addLast(new LoggingHandler(LogLevel.INFO));
	 p.addLast(new ServerBytesFramerDecoder());	
	 p.addLast(new ServerRegisterRequestHandler());
 }

客户端发送的数据少于服务端ReplayingDecoder实现类要求接收的数据

本示例的验证场景:
在这个示例中,客户端发送了12个字节的数据,服务端的ServerBytesFramerDecoder要求接收100个字节的数据,所以没有接收到足够的数据,导致后续的ServerRegisterRequestHandler没有接收到数据。

服务端代码片段

package com.thb.power.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * 服务端的主函数
 * @author thb
 *
 */
public class MainStation {
	
	static final int PORT = Integer.parseInt(System.getProperty("port", "22335"));

	public static void main(String[] args) throws Exception {
		// 配置服务器
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		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 MainStationInitializer());
			
			// 启动服务端
			ChannelFuture f = b.bind(PORT).sync();
			
			// 等待直到server socket关闭
			f.channel().closeFuture().sync();
		} finally {
			// 关闭所有event loops以便终止所有的线程
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
	}

}
package com.thb.power.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class MainStationInitializer extends ChannelInitializer<SocketChannel> {

	 @Override
	 public void initChannel(SocketChannel ch) throws Exception {
		 ChannelPipeline p = ch.pipeline();		
		 
		 p.addLast(new LoggingHandler(LogLevel.INFO));
		 p.addLast(new ServerBytesFramerDecoder());	
		 p.addLast(new ServerRegisterRequestHandler());
	 }
}
package com.thb.power.server;

import java.util.List;

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

public class ServerBytesFramerDecoder  extends ReplayingDecoder<Void> {

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
	    //out.add(in.readBytes(12));				
		out.add(in.readBytes(100));		
	}
}
package com.thb.power.server;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ServerRegisterRequestHandler extends ChannelInboundHandlerAdapter {
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		ByteBuf m = (ByteBuf)msg;
		System.out.println("ServerRegisterRequestHandler: readableBytes: " + m.readableBytes());
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

启动服务端

在这里插入图片描述

启动客户端,并发送12个字节的数据

在这里插入图片描述

观察服务端的输出

在这里插入图片描述
从上面服务端的输出可以发现,LoggingHandler是收到了12个字节的数据,但ServerRegisterRequestHandler没有接收到数据。这是因为ServerBytesFramerDecoder没有接收到足够的数据(期望接收100个字节),也就没有传递给后续的ServerRegisterRequestHandler。

调用checkpoint(S state)传递状态

本示例的验证场景:
在这个示例中,客户端发送了12个字节的数据,服务端的解码器ServerBytesFramerDecoder通过调用checkpoint(S state)来更新解码器的状态,读取数据中的不同的部分。读到期望的内容,传递给后续的ServerRegisterRequestHandler。

这个场景用到了ReplayingDecoder的几个函数,先解释下:

  • checkpoint(S state):这个函数的作用是保存ReplayingDecoder中累加buffer的当前的readerIndex,并且更新当前解码器的状态。
  • checkpoint():这个函数的作用是保存ReplayingDecoder中累加buffer的当前的readerIndex。(本场景中我们没有使用这个参数。但作为对比,罗列出来)
  • state():返回解码器的当前状态。
  • state(S newState):设置解码器的当前状态。(本场景中我们没有使用这个参数。但作为对比,罗列出来)

服务端代码片段

package com.thb.power.server;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * 服务端的主函数
 * @author thb
 *
 */
public class MainStation {
	private static final Logger logger = LogManager.getLogger();
	
	static final int PORT = Integer.parseInt(System.getProperty("port", "22335"));

	public static void main(String[] args) throws Exception {
		logger.traceEntry();
		
		// 配置服务器
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		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 MainStationInitializer());
			
			// 启动服务端
			ChannelFuture f = b.bind(PORT).sync();
			
			// 等待直到server socket关闭
			f.channel().closeFuture().sync();
		} finally {
			// 关闭所有event loops以便终止所有的线程
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
		}
		
		logger.traceExit();
	}

}
package com.thb.power.server;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.thb.power.server.register.ServerRegisterRequestHandler;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class MainStationInitializer extends ChannelInitializer<SocketChannel> {
	private static final Logger logger = LogManager.getLogger();

	 @Override
	 public void initChannel(SocketChannel ch) throws Exception {
		 logger.traceEntry();
		 
		 ChannelPipeline p = ch.pipeline();		
		 
		 p.addLast(new LoggingHandler(LogLevel.INFO));
		 p.addLast(new ServerBytesFramerDecoder());	
		 p.addLast(new ServerRegisterRequestHandler());
		 
		 logger.traceExit();
	 }
}
package com.thb.power.server;

import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

public class ServerBytesFramerDecoder  extends ReplayingDecoder<ServerBytesFramerDecoderState> {
	private static final Logger logger = LogManager.getLogger();
	private byte length;
	
	public ServerBytesFramerDecoder() {
		super(ServerBytesFramerDecoderState.READ_START_MARK);
	}

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		logger.traceEntry();
		
		switch(state()) {
			case READ_START_MARK:
				in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_FUNCTION_CODE);
				// 可以继续往下处理,因为可能字节足够,而且下一个处理状态是READ_FUNCTION_CODE
			case READ_FUNCTION_CODE:
				in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_LENGTH);
				// 可以继续往下处理,因为可能字节足够,而且下一个处理状态是READ_LENGTH
			case READ_LENGTH:
				length = in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_CONTENT);
				// 可以继续往下处理,因为可能字节足够,而且下一个处理状态是READ_CONTENT
			case READ_CONTENT:
				// 此处加3是因为除了内容字段,还有其它2个字段(占用3个字节),一起读取了,往后边的ChannelHandler传递
				ByteBuf content = in.readBytes(length + 3);
				checkpoint(ServerBytesFramerDecoderState.READ_START_MARK);
				out.add(content);
				break;
			default:
				throw new Error("Illegal data.");
		}		
		
		logger.traceExit();
	}
}
package com.thb.power.server;

public enum ServerBytesFramerDecoderState {
	READ_START_MARK,
	READ_FUNCTION_CODE,
	READ_LENGTH,
	READ_CONTENT;	
}
package com.thb.power.server.register;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class ServerRegisterRequestHandler extends ChannelInboundHandlerAdapter {
	
	private static final Logger logger = LogManager.getLogger();
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		logger.traceEntry();
		
		ByteBuf m = (ByteBuf)msg;
		
		logger.info("readableBytes: " + m.readableBytes());
		logger.traceExit();
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

启动服务端

在这里插入图片描述

启动客户端,并发送12个字节的数据

在这里插入图片描述

观察服务端的输出

在这里插入图片描述
从服务端的输出可以看出,ServerRegisterRequestHandler成功接收到了9个字节的数据。

为什么是接收到9个字节的数据呢?
因为在ServerBytesFramerDecoder的decode函数中,读取第1个字节的协议开始符、1个字节的功能码、1个字节的长度字段,这3个字节只是读出来,并没有向后传递。所以12个字节的数据,减去3个字节的数据,向后传递的是9个字节的数据。
此处只是为了说明checkpoint(S state)的场景,真正的业务中,要结合业务实际情况处理,例如判断功能码等。

对比—decode函数一次调用只处理1个状态、一次调用处理多个状态

这个对比的场景:

  • ServerBytesFramerDecoder的decode函数使用了checkpoint(S state)更新状态、保存buffer的当前readerIndex。
  • 用switch语句块对状态进行判断处理。switch语句块中的case分支,是按照状态顺序排列的。
  • 考虑到decode函数输入的buffer可能已经累积了足够的字节,所以 1)一次调用decode函数连续处理几个状态,即case分支执行完成以后,没有使用break语句跳出switch语句块,而是继续往下处理下一个状态。这样性能其实更好一些; 或者 2)也可以每次调用decode函数只处理1个状态,即每个case分支处理完成以后,就用break语句跳出switch语句块,等下次调用decode函数再处理下一个状态,这样性能差一些。

为了进行对比,只列出ServerBytesFramerDecoder类的代码,其它类的代码没有改动。

处理方法1:一次调用decode函数连续处理几个状态
package com.thb.power.server;

import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

public class ServerBytesFramerDecoder  extends ReplayingDecoder<ServerBytesFramerDecoderState> {
	private static final Logger logger = LogManager.getLogger();
	private byte length;
	
	public ServerBytesFramerDecoder() {
		super(ServerBytesFramerDecoderState.READ_START_MARK);
	}

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		logger.traceEntry();
		logger.info("enter decode");
		
		switch(state()) {
			case READ_START_MARK:
				in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_FUNCTION_CODE);
				// 可以继续往下处理,因为可能字节足够,而且下一个处理状态是READ_FUNCTION_CODE				
			case READ_FUNCTION_CODE:
				in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_LENGTH);
				// 可以继续往下处理,因为可能字节足够,而且下一个处理状态是READ_LENGTH				
			case READ_LENGTH:
				length = in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_CONTENT);
				// 可以继续往下处理,因为可能字节足够,而且下一个处理状态是READ_CONTENT				
			case READ_CONTENT:
				// 此处加3是因为除了内容字段,还有其它2个字段(占用3个字节),一起读取了,往后边的ChannelHandler传递
				ByteBuf content = in.readBytes(length + 3);
				checkpoint(ServerBytesFramerDecoderState.READ_START_MARK);
				out.add(content);
				break;
			default:
				throw new Error("Illegal data.");
		}		
		
		logger.traceExit();
	}
}

启动服务端,启动客户端并发送12个字节的数据给服务端,观察服务端的输出:
在这里插入图片描述
从服务端输出可以看出,只进入decode函数一次,就把报文的几个状态都处理完了,将读出的数据传递给了后面的ServerRegisterRequestHandler。为什么传递给后面ServerRegisterRequestHandler是9个字节的数据,上面有解释。

处理方法2:一次调用decode函数只处理1个状态
package com.thb.power.server;

import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

public class ServerBytesFramerDecoder  extends ReplayingDecoder<ServerBytesFramerDecoderState> {
	private static final Logger logger = LogManager.getLogger();
	private byte length;
	
	public ServerBytesFramerDecoder() {
		super(ServerBytesFramerDecoderState.READ_START_MARK);
	}

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		logger.traceEntry();
		logger.info("enter decode");
		
		switch(state()) {
			case READ_START_MARK:
				in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_FUNCTION_CODE);		
				break;
			case READ_FUNCTION_CODE:
				in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_LENGTH);
				break;
			case READ_LENGTH:
				length = in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_CONTENT);				
				break;
			case READ_CONTENT:
				// 此处加3是因为除了内容字段,还有其它2个字段(占用3个字节),一起读取了,往后边的ChannelHandler传递
				ByteBuf content = in.readBytes(length + 3);
				checkpoint(ServerBytesFramerDecoderState.READ_START_MARK);
				out.add(content);
				break;
			default:
				throw new Error("Illegal data.");
		}		
		
		logger.traceExit();
	}
}

启动服务端,启动客户端并发送12个字节的数据给服务端,观察服务端的输出:
在这里插入图片描述
从服务端输出可以看出,因为switch语句块每个case处理完一个状态就break了,所以4个状态就进入decode函数4次,每个状态进入一次。

ReplayingDecoder的decode函数传入的ByteBuf实现类是ReplayingDecoderByteBuf

我们在代码中打印出来decode函数传入的ByteBuf的实现类。

只给出ReplayingDecoder的实现类ServerBytesFramerDecoder的decode函数的代码片段,其它的省略:

package com.thb.power.server;

import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

public class ServerBytesFramerDecoder  extends ReplayingDecoder<ServerBytesFramerDecoderState> {
	private static final Logger logger = LogManager.getLogger();
	private byte length;

	......省略其它代码
	
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		logger.traceEntry();
		logger.info("enter decode");
		
		System.out.println("ByteBuf implemetation class: " + in.getClass().getName());	
		
		......省略其它代码
		
		logger.traceExit();
	}
}

输出:
在这里插入图片描述
从输出可以看出,实现类是io.netty.handler.codec.ReplayingDecoderByteBuf

从ReplayingDecoderByteBuf读出字节不够的时候抛出Error,被ReplayingDecoder的函数捕获

我们首先看看源代码,然后用debug跟踪下在哪个地方捕获的Error。

package com.thb.power.server;

import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

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

public class ServerBytesFramerDecoder  extends ReplayingDecoder<ServerBytesFramerDecoderState> {
	private static final Logger logger = LogManager.getLogger();
	private byte length;
	
......省略代码

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
		logger.traceEntry();
		logger.info("enter decode");
		
		switch(state()) {
			case READ_START_MARK:
				in.readByte();
				checkpoint(ServerBytesFramerDecoderState.READ_FUNCTION_CODE);		
				// 可以继续往下处理,因为可能字节足够,而且下一个处理状态
		}		
		
		......省略代码
		
		logger.traceExit();
	}
}
  • protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out)函数入参in的类型是ReplayingDecoderByteBuf。
  • ReplayingDecoderByteBuf函数中readByte()函数的实现:
@Override
public byte readByte() {
    checkReadableBytes(1);
    return buffer.readByte();
}

可以发现,readByte()首先调用了checkReadableBytes(1)进行检查。

  • checkReadableBytes(int readableBytes)函数的实现:
private void checkReadableBytes(int readableBytes) {
    if (buffer.readableBytes() < readableBytes) {
        throw REPLAY;
    }
}

如果可读字节数不够,抛出REPLAY,这个REPLAY是Signal类型:
在这里插入图片描述
Signal是Error的子类:
在这里插入图片描述

为了直观地看捕获这个Error,利用debug来跟踪好一些。可以在eclipse中用debug模式运行服务端,在cmd窗口下运行客户端并发送数据。
在这里插入图片描述

在ReplayingDecoder的callDecode函数,捕获了抛出的Error:
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值