You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java接收C++硬件TCP端口二进制结构化数据的实现咨询

问题根源分析

你遇到的乱码是因为错误地用文本流读取二进制数据:

  • 硬件发送的是C++结构体打包后的二进制字节流,而非可读文本;
  • 你原代码中的BufferedReader和InputStreamReader是用于处理字符编码的文本数据,直接将二进制字节转成字符串必然出现乱码。
解决方案步骤

1. 替换字符流为字节流读取

直接使用InputStream读取原始字节,因为二进制数据包是固定长度的(根据C++结构体计算),需要一次性读取完整的数据包。

2. 计算C++结构体的总长度

根据你提供的C++结构体和#pragma pack(4)对齐规则,先确定数据包总长度:

  • char imei[16] → 16字节
  • uint16_t read_cycle/data_count/bat_p/bat_v/chsum → 各2字节,共10字节
  • double data → 8字节
  • int16_t interval[METER_READ_COUNT-1] → 2*(METER_READ_COUNT-1)字节(需替换为硬件实际定义的METER_READ_COUNT值)
  • 按4字节对齐后,总长度需取上述总和向上对齐到4的倍数。

3. 二进制数据解析(Java实现)

用ByteBuffer解析字节数组,注意匹配C的小端字节序(C默认字节序),并映射为Java对象。

修改后的完整代码

import java.net.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class BinaryTcpServer {

    private ServerSocket serverSocket;
    // 替换为硬件实际定义的METER_READ_COUNT值,示例设为5
    private static final int METER_READ_COUNT = 5;
    // 计算后的完整数据包长度:16+2+2+8 + 2*(5-1) +2+2+2 = 42字节(按pack(4)对齐后确认)
    private static final int PACKET_LENGTH = 42;

    public void start(int port) {
        try {
            serverSocket = new ServerSocket(port);
            while (true)
                new ClientHandler(serverSocket.accept()).start();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            stop();
        }
    }

    public void stop() {
        try {
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static class ClientHandler extends Thread {
        private Socket clientSocket;
        private InputStream in;

        public ClientHandler(Socket socket) {
            this.clientSocket = socket;
        }

        public void run() {
            try {
                in = clientSocket.getInputStream();
                byte[] buffer = new byte[PACKET_LENGTH];
                int bytesRead;

                // 循环读取完整数据包
                while ((bytesRead = in.read(buffer)) != -1) {
                    if (bytesRead == PACKET_LENGTH) {
                        NbPacket packet = parsePacket(buffer);
                        printPacket(packet);
                    } else {
                        System.out.println("收到不完整数据包,长度:" + bytesRead);
                    }
                }
                in.close();
                clientSocket.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private NbPacket parsePacket(byte[] buffer) {
            NbPacket packet = new NbPacket();
            ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
            // 设置为小端字节序,匹配C++默认字节序
            byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

            // 解析IMEI:读取16字节并去除末尾空字符
            byte[] imeiBytes = new byte[16];
            byteBuffer.get(imeiBytes);
            packet.setImei(new String(imeiBytes).trim());

            // 解析uint16_t字段(Java用short接收,需处理无符号时转int:value & 0xFFFF)
            packet.setReadCycle(byteBuffer.getShort());
            packet.setDataCount(byteBuffer.getShort());

            // 解析double
            packet.setData(byteBuffer.getDouble());

            // 解析interval数组
            int intervalLen = METER_READ_COUNT - 1;
            short[] interval = new short[intervalLen];
            for (int i = 0; i < intervalLen; i++) {
                interval[i] = byteBuffer.getShort();
            }
            packet.setInterval(interval);

            // 解析剩余字段
            packet.setBatP(byteBuffer.getShort());
            packet.setBatV(byteBuffer.getShort());
            packet.setChsum(byteBuffer.getShort());

            return packet;
        }

        private void printPacket(NbPacket packet) {
            System.out.println("解析后数据包:");
            System.out.println("IMEI: " + packet.getImei());
            System.out.println("读取周期: " + packet.getReadCycle());
            System.out.println("数据数量: " + packet.getDataCount());
            System.out.println("数据值: " + packet.getData());
            System.out.print("间隔数组: ");
            for (short s : packet.getInterval()) {
                System.out.print(s + " ");
            }
            System.out.println();
            System.out.println("电池百分比: " + packet.getBatP());
            System.out.println("电池电压: " + packet.getBatV());
            System.out.println("校验和: " + packet.getChsum());
            System.out.println("------------------------");
        }
    }

    // 映射C++结构体的Java类
    public static class NbPacket {
        private String imei;
        private short readCycle;
        private short dataCount;
        private double data;
        private short[] interval;
        private short batP;
        private short batV;
        private short chsum;

        // Getter & Setter
        public String getImei() { return imei; }
        public void setImei(String imei) { this.imei = imei; }
        public short getReadCycle() { return readCycle; }
        public void setReadCycle(short readCycle) { this.readCycle = readCycle; }
        public short getDataCount() { return dataCount; }
        public void setDataCount(short dataCount) { this.dataCount = dataCount; }
        public double getData() { return data; }
        public void setData(double data) { this.data = data; }
        public short[] getInterval() { return interval; }
        public void setInterval(short[] interval) { this.interval = interval; }
        public short getBatP() { return batP; }
        public void setBatP(short batP) { this.batP = batP; }
        public short getBatV() { return batV; }
        public void setBatV(short batV) { this.batV = batV; }
        public short getChsum() { return chsum; }
        public void setChsum(short chsum) { this.chsum = chsum; }
    }

    public static void main(String[] args) {
        BinaryTcpServer server = new BinaryTcpServer();
        server.start(5555);
    }
}

4. 关键注意事项

  • 字节序校验:如果解析后数值异常,需确认硬件端的字节序(大端/小端),调整ByteBuffer的字节序设置。
  • 无符号类型处理:C++的uint16_t范围是0-65535,Javashort是有符号类型,若数值超过32767,需用int value = shortValue & 0xFFFF转换为无符号整数。
  • 校验和验证:务必实现硬件端的校验算法(如CRC16、累加和),对比chsum字段确保数据包未损坏。
  • 数据包长度校准:根据硬件实际的METER_READ_COUNT值重新计算PACKET_LENGTH,避免读取不完整数据。

内容的提问来源于stack exchange,提问作者Elena

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 21:35:41