Netty客户端接收不到服务端发送的数据问题

发布于:2024-12-18 ⋅ 阅读:(109) ⋅ 点赞:(0)


前言

环境
JDK:64位 Jdk1.8
Netty:4.1.39.Final

问题描述

项目中使用Netty接受客户端的消息,客户端为硬件设备,在接受数据后发送数据到服务端。
同时因为客户端没有联网,所以可能会因为开关机等原因导致时间不准确,因此需要服务端添加一个校验时间的操作,如果时间差距过大则发送重新设置时间的指令。

相关代码

@Slf4j
@Component
public class NettyServer {
    @Autowired
    private 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> {
    
    @Override
    protected 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
     */
    @Override
    protected 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> {
    
    @Override
    protected 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



网站公告

今日签到

点亮在社区的每一天
去签到