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
相关产品推荐
相关产品推荐

