文章目錄
- 前言
- 問題描述
- 相關代碼
- 解決方法
前言
環境
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