文章目录
- 前言
- 问题描述
- 相关代码
- 解决方法
前言
环境
JDK:64位 jdk1.8.0_201
Netty:4.1.39.Final
问题描述
项目中使用Netty接受客户端的消息,客户端为硬件设备,在接受数据后发送数据到服务端。
同时因为客户端没有联网,所以可能会因为开关机等原因导致时间不准确,因此需要服务端添加一个校验时间的操作,如果时间差距过大则发送重新设置时间的指令。
相关代码
@Slf4j
@Component
public class NettyServer {@Autowiredprivate SocketConfig socketConfig;private EventLoopGroup bossGroup = null;private EventLoopGroup workerGroup = null;public void start() throws Exception {bossGroup = new NioEventLoopGroup(1);workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(2048, 2048, 2048)).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new DeviceChannelInitHandler());//无关代码省略...} catch (Exception e) {log.info("netty异常:", e);} }...
}
处理器
@Slf4j
public class DeviceChannelInitHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {...//使用自定义分割符customizeSplitHandler(socketChannel, true, "$");socketChannel.pipeline().addLast(new StringDecoder());// 添加自定义的业务处理器socketChannel.pipeline().addLast(new DeviceServiceHandler());socketChannel.pipeline().addLast(new StringEncoder());...}/*** 自定义分隔符处理器** @param socketChannel* @param stripDelimiter* @param split*/private static void customizeSplitHandler(SocketChannel socketChannel, boolean stripDelimiter, String split) {ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);buffer.writeBytes(split.getBytes());socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(2048, stripDelimiter, buffer));}
}
业务处理器
@Slf4j
public class DeviceServiceHandler extends SimpleChannelInboundHandler<String> {/*** 解码协议(字符串转换)** @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}* belongs to* @param dateStr the message to handle* @throws Exception*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String dateStr) throws Exception {...//如果设备发送的时间和当前系统时间差距太大,则修正设备时间String sendDate = DateUtil.getStringCustomFormatByDate(new Date(), "yyyyMMddHHmmss");String command = "#SETTIME" + sendDate + "!$";//发送指令ctx.writeAndFlush(command);...}
}
解决方法
经过逐一排查,查看git的历史记录,发现原因是之前的同事将字符串编码器 StringEncoder 放在了业务处理器DeviceServiceHandler 的下面。
导致在数据发送时,业务处理器DeviceServiceHandler 会先处理数据,但此时数据还没有被编码为字节数据。由于 StringEncoder 还没有被调用,数据将以未编码的形式发送,这可能导致客户端无法正确解析数据,从而无法接收到正确的数据。
@Slf4j
public class DeviceChannelInitHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {...customizeSplitHandler(socketChannel, true, "$");socketChannel.pipeline().addLast(new StringDecoder());//将字符串编码器 StringEncoder 放在业务处理器上面,客户端即可收到数据socketChannel.pipeline().addLast(new StringEncoder());socketChannel.pipeline().addLast(new DeviceServiceHandler());...}
}
如果对Netty中入站和出站处理器还不是很了解,可以看以下这篇文章:
Netty组件Handler & Pipeline