无.proto服务定义时,如何通过Java Spring Boot从使用Protobuf的GPS设备获取数据?
首先得纠正一个误区:Protobuf只是数据序列化协议,和gRPC的Service没有强制绑定关系。很多IoT设备(比如你的GPS设备)并不会用gRPC框架,而是直接把Protobuf序列化后的二进制数据通过TCP/UDP、HTTP等协议发送,完全不需要在.proto文件里定义Service。下面分几种常见场景给你讲解实现方式:
场景1:设备通过TCP/UDP发送Protobuf二进制数据
这类情况是IoT设备最常用的,设备会主动向服务器的TCP/UDP端口发送序列化后的GPS数据。你可以用Netty或者Spring Integration来搭建服务端接收数据:
步骤1:根据现有.proto文件生成Java类
不管哪种场景,第一步都是把.proto文件转换成Java实体类。你可以用Maven插件自动生成,在pom.xml中添加以下配置:
<build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.7.0</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.24.4:exe:${os.detected.classifier}</protocArtifact> <pluginId>java</pluginId> <!-- 如果需要支持gRPC可以添加grpc-java插件,这里不需要就不用加 --> </configuration> <executions> <execution> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
执行mvn compile后,插件会自动在target/generated-sources目录生成对应的Java类。
步骤2:用Netty搭建TCP服务端解析数据
Netty是处理高性能网络通信的首选框架,适合接收IoT设备的TCP数据流:
- 引入Netty依赖:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.100.Final</version> </dependency>
- 编写Netty服务端和数据处理Handler:
@Component public class GpsTcpServer { @Value("${gps.tcp.port:8888}") private int port; @PostConstruct public void start() throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(1); 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 ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); // 如果设备用固定长度前缀解决粘包拆包,添加长度解码器 p.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); // 自定义Handler解析Protobuf数据 p.addLast(new GpsProtobufHandler()); } }); // 绑定端口启动服务 ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } // 自定义Protobuf解析Handler public class GpsProtobufHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { // 将ByteBuf转换为字节数组 byte[] data = new byte[msg.readableBytes()]; msg.readBytes(data); // 解析为Protobuf生成的Java对象(假设你的GPS数据类是GpsData) GpsData gpsData = GpsData.parseFrom(data); // 这里处理解析后的GPS数据,比如保存到数据库、发送到MQ等 System.out.println("收到GPS数据:" + gpsData); ctx.writeAndFlush(Unpooled.copiedBuffer("数据已接收".getBytes())); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
注意:如果设备发送的数据存在粘包拆包问题,需要根据设备的协议规则添加对应的解码器(比如长度前缀、分隔符等),上面示例用的是
LengthFieldBasedFrameDecoder,适合设备发送带固定长度前缀的数据包。
场景2:设备通过HTTP POST发送Protobuf二进制数据
如果设备支持HTTP协议,会把Protobuf二进制数据作为请求体POST到服务器,这种情况下用Spring Boot的Controller就能轻松处理:
步骤1:同样先生成Protobuf Java类(和场景1步骤1一致)
步骤2:编写接收数据的Controller
@RestController @RequestMapping("/gps") public class GpsDataController { @PostMapping(value = "/receive", consumes = "application/x-protobuf") public ResponseEntity<String> receiveGpsData(@RequestBody byte[] protobufData) { try { // 解析Protobuf数据 GpsData gpsData = GpsData.parseFrom(protobufData); // 处理数据逻辑 System.out.println("接收HTTP方式的GPS数据:" + gpsData); return ResponseEntity.ok("数据接收成功"); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); return ResponseEntity.badRequest().body("Protobuf数据解析失败"); } } }
这里的关键是设置consumes = "application/x-protobuf",告诉Spring Boot接收的是Protobuf二进制格式的请求体。
特殊情况:设备实际用gRPC但未提供Service定义
如果设备确实是基于gRPC通信,但厂商没给Service定义,你可以通过抓包分析设备的gRPC请求(比如用Wireshark),反推Service的方法定义,然后补全.proto文件的Service部分,再用Spring Boot整合gRPC服务端来接收数据。不过这种情况在GPS设备中比较少见,大部分都是直接发送二进制数据。
内容的提问来源于stack exchange,提问作者Mahdiyar

