说明
- io.netty.handler.codec.DelimiterBasedFrameDecoder是ByteToMessageDecoder的一个实现类,用一个或多个分割符拆分接收到的ByteBuf。这个主要用于解析分隔符在帧的末尾的场景。注意,当分割符在帧的开头,那么去掉分隔符后解析出来的帧的长度是0,所以不能只用帧开头的分隔符,在帧末尾的分隔符也要使用。
- 在构造DelimiterBasedFrameDecoder实例的时候,允许声明多个分隔符。解析的时候,这几个分割符都会使用。用每个分隔符来解析一次,每次解析的结果都会向ChannelPipeline中后面的ChannelHandler传递。在执行内部解析逻辑的时候,DelimiterBasedFrameDecoder会先使用能产生最短帧的分隔符。当使用多个分割符的时候,建议在构建DelimiterBasedFrameDecoder实例的时候,是否去掉分隔符的参数填写true,这样后面的ChannelHandler收到的数据最终是去掉分割符后的数据;如果是否去掉分隔符的参数填写false,以头尾两个分割符举例,那么后面的ChannelHandler第一次收到的数据是头部分隔符,第二次收到的是剩余的其它数据(包含尾部分隔符)。
在构造DelimiterBasedFrameDecoder实例的时候,用到了下面几个参数,用来控制拆分行为:
- maxFrameLength:拆分的帧最多有多少字节。
- stripDelimiter:拆分后的帧是否去掉分隔符,默认值为true。
- failFast:默认值为true。当为true的时候,如果解码器发现帧的长度大于maxFrameLength,就会抛出 TooLongFrameException,而不管是否真正读取了整个帧。如果值为false,实际读取的字节数大于maxFrameLength时,才会抛出TooLongFrameException。
- delimiters:多个分割符。
- delimiter:单个分割符。
风险:用DelimiterBasedFrameDecoder进行帧的分割,有可能出现分隔符和报文中的正常内容冲突的情况,即报文中的正常内容正好含有分割符。如果出现这种情况,可以考虑使用更底层的ByteToMessageDecoder或者ReplayingDecoder。
代码示例
分割符在报文的结尾,正常解析
本示例验证的场景:
- 客户端发送了一个12字节的报文给服务端。报文开头的字节内容是0x68,结尾的字节内容是0x16。
- 服务端用了DelimiterBasedFrameDecoder来基于分隔符解析帧。解析的时候设置帧的最大字节数是12,去掉分隔符,用报文结尾的0x16作为分隔符。解析完成后,将去掉分隔符的帧传送给后面的ChannelHandler。
服务端代码片段
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.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
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 DelimiterBasedFrameDecoder(12, true, Unpooled.wrappedBuffer(new byte[] {0x16})));
p.addLast(new ServerRegisterRequestHandler());
logger.traceExit();
}
}
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收到了11个字节的数据,这是正确的。因为整个帧12个字节,DelimiterBasedFrameDecoder在解析的时候,我们通过参数控制,去掉了1个字节的分隔符。
分割符在报文的开头,解析出来报文长度是0
本示例验证的场景:
- 客户端发送了一个12字节的报文给服务端。报文开头的字节内容是0x68,结尾的字节内容是0x16。
- 服务端用了DelimiterBasedFrameDecoder来基于分隔符解析帧。解析的时候设置帧的最大字节数是12,去掉分隔符,用报文开头的0x68作为分隔符,解析出来报文长度是0。解析完成后,后面的ChannelHandler收到的报文长度是0。
服务端代码片段
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.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
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));
// 基于分隔符将收到的ByteBuf解析成完整的报文
p.addLast(new DelimiterBasedFrameDecoder(12, true, Unpooled.wrappedBuffer(new byte[] {0x68})));
p.addLast(new ServerRegisterRequestHandler());
logger.traceExit();
}
}
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个字节的报文
观察服务端的输出
从输出可以看出,DelimiterBasedFrameDecoder后面的ServerRegisterRequestHandler只收到了0字节的数据。说明DelimiterBasedFrameDecoder做了解析动作,但因为分隔符用的是报文的开头字符,去掉分割符以后,解析的报文长度为0,没有达到预期的目标。
报文头、尾的分隔符都使用,构建DelimiterBasedFrameDecoder实例时去掉分隔符参数填写true
本示例验证的场景:
- 客户端发送了一个12字节的报文给服务端。报文开头的字节内容是0x68,结尾的字节内容是0x16。
- 服务端用了DelimiterBasedFrameDecoder来基于分隔符解析帧。创建DelimiterBasedFrameDecoder实例的时候,设置帧的最大字节数是12,去掉分隔符,分隔符填写了两个(报文头的0x68和报文尾的0x16)。
- DelimiterBasedFrameDecoder后面的ChannelHandler收到了两次解析后的帧,第一次为0个字节,第二次为10个字节。最终结果去掉了头尾分隔符,解析成功。
服务端代码片段
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.constant.PowerConstants;
import com.thb.power.server.register.ServerRegisterRequestHandler;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
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));
// 基于分隔符将收到的ByteBuf解析成完整的报文
// 用报文开头的字符、结尾的字符作为分隔符
p.addLast(new DelimiterBasedFrameDecoder(12, true,
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_PROTOCOL_END_MARK}),
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_PROTOCOL_START_MARK})));
p.addLast(new ServerRegisterRequestHandler());
logger.traceExit();
}
}
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());
// 将收到的报文的每个字节转换为十六进制打印出来
for (int i = 0; i < m.readableBytes(); i++) {
short s = m.getUnsignedByte(m.readerIndex() + i);
StringBuilder sb = new StringBuilder(Integer.toHexString(s));
// 如果转换后的字符串长度不够2,在前面补"0",这个是为了美观,让一个字节转换为十六进制显示2位
if (sb.length() % 2 != 0) {
sb.insert(0, "0");
}
System.out.print(sb + " ");
}
System.out.println();
logger.traceExit();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
启动服务端
启动客户端,并向服务端发送12个字节的业务数据
观察服务端的输出
从上面的输出可以看出,DelimiterBasedFrameDecoder后面的ServerRegisterRequestHandler收到了两次解析的数据,第一次是0个字节,第二次是10个字节,最终解析结果正确。
报文头、尾的分隔符都使用,构建DelimiterBasedFrameDecoder实例时去掉分隔符参数填写false
本示例验证的场景:
- 客户端发送了一个12字节的报文给服务端。报文开头的字节内容是0x68,结尾的字节内容是0x16。
- 服务端用了DelimiterBasedFrameDecoder来基于分隔符解析帧。创建DelimiterBasedFrameDecoder实例的时候,设置帧的最大字节数是12,不去掉分隔符,分隔符填写了两个(报文头的0x68和报文尾的0x16)。
- DelimiterBasedFrameDecoder后面的ChannelHandler收到了两次解析后的帧,第一次为1个字节,第二次为11个字节。
服务端代码片段
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.constant.PowerConstants;
import com.thb.power.server.register.ServerRegisterRequestHandler;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
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));
// 基于分隔符将收到的ByteBuf解析成完整的报文
// 用报文开头的字符、结尾的字符作为分隔符
p.addLast(new DelimiterBasedFrameDecoder(12, false,
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_PROTOCOL_END_MARK}),
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_PROTOCOL_START_MARK})));
p.addLast(new ServerRegisterRequestHandler());
logger.traceExit();
}
}
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());
// 将收到的报文的每个字节转换为十六进制打印出来
for (int i = 0; i < m.readableBytes(); i++) {
short s = m.getUnsignedByte(m.readerIndex() + i);
StringBuilder sb = new StringBuilder(Integer.toHexString(s));
// 如果转换后的字符串长度不够2,在前面补"0",这个是为了美观,让一个字节转换为十六进制显示2位
if (sb.length() % 2 != 0) {
sb.insert(0, "0");
}
System.out.print(sb + " ");
}
System.out.println();
logger.traceExit();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
启动服务端
启动客户端,并向服务端发送12个字节的业务数据
观察服务端的输出
从输出可以看出,DelimiterBasedFrameDecoder后面的ServerRegisterRequestHandler收到了两次解析的数据,第一次是1个字节,就是报文头部的分隔符;第二次是11个字节,报文的内容和尾部的分割符。–感觉这样使用的场景不多
将DelimiterBasedFrameDecoder解析后的ByteBuf的内容用更加美化的方式显示出来
本示例验证的场景:
- 客户端发送了一个12字节的报文给服务端。报文开头的字节内容是0x68,结尾的字节内容是0x16。
- 服务端用了DelimiterBasedFrameDecoder来基于分隔符解析帧。创建DelimiterBasedFrameDecoder实例的时候,设置帧的最大字节数是12,去掉分隔符,分隔符填写了两个(报文头的0x68和报文尾的0x16)。
- DelimiterBasedFrameDecoder后面的ChannelHandler收到了解析后的帧,用ByteBufUtil的appendPrettyHexDump函数将收到的ByteBuf的内容用更加美化的方式显示出来,方便人阅读。
服务端代码片段
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.constant.PowerConstants;
import com.thb.power.handler.VerifyChecksumHandler;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
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));
// 基于分隔符将收到的ByteBuf解析成完整的报文
// 用报文开头的字符、结尾的字符作为分隔符
p.addLast(new DelimiterBasedFrameDecoder(12, true,
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_PROTOCOL_END_MARK}),
Unpooled.wrappedBuffer(new byte[] {PowerConstants.SOCKET_PROTOCOL_START_MARK})));
p.addLast(new VerifyChecksumHandler());
logger.traceExit();
}
}
package com.thb.power.handler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.CorruptedFrameException;
/**
* 验证接收的报文的校验和
* @author thb
*
*/
public class VerifyChecksumHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LogManager.getLogger();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
logger.traceEntry();
if (msg instanceof ByteBuf) {
ByteBuf m = (ByteBuf)msg;
logger.info("readableBytes: " + m.readableBytes());
// 将收到的报文的每个字节转换为十六进制打印出来
StringBuilder dump = new StringBuilder();
ByteBufUtil.appendPrettyHexDump(dump, m, m.readerIndex(), m.readableBytes());
System.out.println(dump);
} else {
// 这个数据不需要向后传递,因为数据已经错误了
// 传入的对象msg如果实现了ReferenceCounted接口,那么显式就释放
if (msg instanceof ReferenceCounted) {
((ReferenceCounted)msg).release();
}
throw new CorruptedFrameException("received illegal data");
}
logger.traceExit();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
package com.thb.power.constant;
/**
* 保存了一些常数
* @author thb
*
*/
public final class PowerConstants {
/**
* 定义了私有构造函数,防止被外部实例化
*/
private PowerConstants() {
}
/**
* 起始符
*/
public static final byte SOCKET_PROTOCOL_START_MARK = (byte)0x68;
/**
* 结束符
*/
public static final byte SOCKET_PROTOCOL_END_MARK = (byte)0x16;
}
启动服务端
启动客户端,并向服务端发送12个字节的业务数据
观察服务端的输出
可以看到,VerifyChecksumHandler将收到的数据用美化的方式打印了出来