Netty:DelimiterBasedFrameDecoder分析

说明

  • 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将收到的数据用美化的方式打印了出来

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值