近期做物联网使用到了netty,遇到了不少问题,一种问题是粘包,另一种是拆包,虽然相对于我的项目不是什么大的问题,但是如果要对于有些项目数据可靠性来说这可是大的问题。
顺便提一下我的项目中的需求,我做的是智能大棚项目,dtu每隔一段时间会把轮询中的感应器数据,比如温度、湿度、ph值传给服务器。前期我设计每半分钟传送数据一次,看报得太密没有必要,就两分钟一次数据传送,dtu传送时除非程序dubug时断点或网络阻塞才会出现粘包现象,由于本身接收到的moudbu数据很少,出现过拆包现象的少,即便我不考虑粘包和拆包问题也没有关系。但做为程序员的我们要把尽可能考虑的问题都考虑到,把能用的数据尽可能都用到,这就不得不考虑了,粘包问题好解决,只要按其协议截取bytes字节数据就可以了——传感器说明书中有的,多种传感器要根据收集到数据地址区分就可以了。但是拆包问题就不是一个好解决的事了,我偿试过网上那种在编码器中解决的方案,一般都是看解码器中数够不够长度,够了读取后交给转码下面操作,这种方式不适合我的需求:
1、数据不是定长数据;
2、想根据地址位动态获取数据长度,要使用到spring注入其他数据便于查询,没有找到解码器能被sping管理的方法。
但查询了国内很多网站,都是用的此方法,大部分都是定长或者字段中就有字节长度。没有办法只有放弃了。近几天与chatGPT会话谈论时,发现了它给的不少问题让我眼前一亮,就试着问了它这个问题,他给我的回答让我眼前一亮,直接上代码吧:
public class NettyServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
private StringBuilder messageBuilder = new StringBuilder();
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
messageBuilder.append(msg);
if (msg.endsWith("!")) {
String message = messageBuilder.toString();
messageBuilder = new StringBuilder();
System.out.println("Received message: " + message);
}
}
});
}
});
ChannelFuture future = bootstrap.bind(8888).sync();
System.out.println("Server started on port 8888");
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
我们可以考虑使用StringBuilder来缓存读到的数据,在Handler中可以依赖注入其它类非常方便,截取bytes到查询到的长度后,剩余的再重新new StringBuilder,并放进去作为开头就可以了,由于StringBuilder写在每个childHandler类中,不必考虑线程安全问题,同时写在读取方法read外部,上一次未截取完的信息下一次接着还可以使用,这样粘包和拆包问题都解决了。
希望这个思路能帮助大家!