JT/T 1078 协议流程分析
一、概述
1.1 协议简介
JT/T 1078 是《道路运输车辆卫星定位系统车载视频终端通信协议》,基于 JT/T 808 部标协议扩展,主要用于车载视频监控系统的远程数据传输和控制。
应用场景:
- "两客一危"车辆(客运、旅游包车、危险品运输车)
- 重型货车(12 吨以上)
- 校车等特种车辆
1.2 系统架构
┌─────────────────────────────────────────────────────────┐
│ WVP-GB28181-pro │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 前端页面 │ │ RESTful API │ │
│ │ (Vue-admin) │◄──►│ (Controller) │ │
│ └───────────────┘ └───────┬───────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ JT1078Template │ │
│ │ (指令模板) │ │
│ └─────────┬─────────┘ │
│ │ │
│ ┌────────────────────────────▼──────────────────────┐ │
│ │ SessionManager (会话管理) │ │
│ └────────────────────────────┬──────────────────────┘ │
│ │ │
│ ┌────────────────────────────▼──────────────────────┐ │
│ │ Netty Pipeline (TCP 通信核心) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Decoder │ │ Handler │ │ Encoder │ │ │
│ │ │ 解码器 │──►│ 处理器 │──►│ 编码器 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
└───────────────────────────────┼─────────────────────────┘
│ TCP/IP
▼
┌───────────────────────┐
│ 车载终端设备 │
│ (海康/大华/宇视等) │
└───────────────────────┘
1.3 技术栈
| 组件 | 技术选型 | 说明 |
|---|---|---|
| 网络框架 | Netty 4.x | NIO 异步事件驱动 |
| 协议版本 | JT/T 808-2013/2019 | 兼容两个版本 |
| 数据编码 | GBK / BCD 码 | 中文字符和数字编码 |
| 校验方式 | BCC 异或校验 | 8 位异或校验和 |
| 连接方式 | TCP 长连接 | 端口默认 21078 |
| 心跳间隔 | 60 秒 | 可配置 |
| 超时断开 | 10 分钟 | 无活动自动清理 |
二、通信协议详解
2.1 消息帧结构
┌──────────────────────────────────────────────────────────┐
│ 消息头 │
│ (Header: 12-28 字节,取决于协议版本) │
├────────────┬────────────┬────────────┬─────────┬─────────┤
│ 消息 ID │ 消息属性字 │ 终端手机号 │ 流水号 │ 分包信息 │
│ (2 字节) │ (2 字节) │ (6/10 字节) │(2 字节) │(4 字节) │
└────────────┴────────────┴────────────┴─────────┴─────────┘
│ 消息体 │
│ (0-10000+ 字节) │
├──────────────────────────────────────────────────────────┤
│ 校验码 (1 字节) │
│ (BCC: 从消息头到消息体异或) │
└──────────────────────────────────────────────────────────┘
2.1.1 消息头字段说明
消息 ID (2 字节)
- 标识消息类型,如 0x0100 表示终端注册
- 范围:0x0000 - 0xFFFF
消息属性字 (2 字节)
Bit 15-14: 保留
Bit 13 : 分包标志 (0=不分包,1=分包)
Bit 12 : 加密标志 (0=不加密,1=加密)
Bit 11-0 : 消息体长度
终端手机号
- 2013 版:6 字节(BCD 编码)
- 2019 版:10 字节(BCD 编码,不足补 0)
流水号 (2 字节)
- 消息发送计数器,0-65535 循环
- 用于匹配请求和应答
分包信息 (仅当 Bit13=1 时存在)
- 总包数 (2 字节)
- 包序号 (2 字节)
2.2 转义规则
为避免消息中出现与帧起始符 (0x7E) 冲突的数据,需要进行转义:
| 原始字节 | 转义后 | 说明 |
|---|---|---|
| 0x7E | 0x7D 0x02 | 帧起始符转义 |
| 0x7D | 0x7D 0x01 | 转义符本身转义 |
示例:
原始数据:7E 01 00 7D 7E 02 7E
转义后: 7D02 01 00 7D01 7D02 02 7D02
↑ ↑ ↑
帧首 7D 7E
2.3 校验码计算 (BCC)
java
public static byte bcc(ByteBuf byteBuf) {
byte cs = 0;
while (byteBuf.isReadable())
cs ^= byteBuf.readByte(); // 逐字节异或
return cs;
}
计算范围: 从消息头第一个字节到消息体最后一个字节(不含校验码本身)
三、核心业务流程
3.1 设备注册流程 ⭐
Jt808EncoderCmd 数据库 Ijt1078Service J0100 Handler Jt808Decoder Netty Server 车载终端 Jt808EncoderCmd 数据库 Ijt1078Service J0100 Handler Jt808Decoder Netty Server 车载终端 包含省份、城市、 厂商、型号、车牌等 读取省份 ID、城市 ID 制造商 ID(5 字节) 终端型号 (30 字节) 终端 ID(7 字节) 车牌颜色、车牌号 收到成功应答 保存鉴权码 注册失败 检查配置重试 alt [设备已授权] [设备未授权] TCP Connect (建立连接) 创建 Session 发送注册消息 MsgID: 0x0100 1. 帧定界 (0x7E) 2. 反转义 3. BCC 校验 调用 decode() 解析消息体 getDevice(手机号) 查询设备是否授权 返回设备记录 JTDevice 对象 生成鉴权码 (UUID) 构建注册应答 MsgID: 0x8100 Result: 0 (成功) Code: UUID 组包 + 转义 + BCC 发送应答 updateDevice(设备信息) 更新设备状态=在线 注册时间=Now 无记录 null 构建注册应答 MsgID: 0x8100 Result: 1 (失败) 发送拒绝 断开连接
注册消息数据结构 (0x0100):
偏移量 字段 长度 类型 说明
0 省份 ID 2 WORD 行政区划代码前 4 位
2 城市 ID 2 WORD 行政区划代码后 4 位
4 制造商 ID 5 STRING 5 位大写字母和数字
9 终端型号 30 STRING 30 位字母数字
39 终端 ID 7 STRING 7 位字母数字
46 车牌颜色 1 BYTE 0-未上牌,1-蓝,2-黄等
47 车牌号 N STRING GBK 编码,剩余所有字节
注册应答数据结构 (0x8100):
偏移量 字段 长度 类型 说明
0 应答流水号 2 WORD 对应注册消息的流水号
2 应答结果 1 BYTE 0=成功,1=失败,2=车已存在
3 鉴权码 N STRING 失败时为空,成功时为 UUID
3.2 终端鉴权流程
Service Session J0102 Handler 车载终端 Service Session J0102 Handler 车载终端 设备正式在线 可开始业务通信 鉴权失败 重新注册 alt [鉴权码匹配] [鉴权码不匹配] 发送鉴权消息 MsgID: 0x0102 AuthCode: UUID 读取鉴权码 getAuthenticationCode() 返回注册的鉴权码 updateDevice(状态=在线) 通用应答 MsgID: 0x0001 Result: 0 (成功) 通用应答 MsgID: 0x0001 Result: 1 (失败)
3.3 心跳保活流程
J0002 Handler IdleStateHandler 车载终端 J0002 Handler IdleStateHandler 车载终端 loop [每 60 秒] 10 分钟无读写 触发重连机制 心跳消息 MsgID: 0x0002 通用应答 MsgID: 0x0001 主动断开连接
代码实现:
java
// J0002.java
@MsgId(id = "0002")
public class J0002 extends Re {
@Override
protected Rs handler(Header header, Session session, Ijt1078Service service) {
log.info("[终端心跳] {}", header.getPhoneNumber());
J8001 j8001 = new J8001();
j8001.setRespNo(header.getSn());
j8001.setRespId(header.getMsgId());
j8001.setResult(J8001.SUCCESS);
return j8001;
}
}
3.4 位置信息上报流程
Redis 缓存 Ijt1078Service J0200 Handler 车载终端 Redis 缓存 Ijt1078Service J0200 Handler 车载终端 包含经纬度、 速度、方向、时间 纬度 (4 字节) 经度 (4 字节) 高程 (2 字节) 速度 (2 字节) 方向 (1 字节) 时间 (6 字节 BCD) loop [定时上报 (如 10 秒)] opt [附加信息] 位置汇报 MsgID: 0x0200 解析位置信息 updateDevicePosition() 更新设备位置缓存 通用应答 MsgID: 0x0001 位置附加信息 里程、油量、报警等
位置数据结构 (0x0200):
偏移量 字段 长度 类型 说明
0 纬度 4 INT32 度 * 10^6
4 经度 4 INT32 度 * 10^6
8 高程 2 WORD 米
10 速度 2 WORD 0.1 km/h
12 方向 1 BYTE 0-360 度
13 时间 6 BCD YY-MM-DD-HH-MM-SS
19 附加信息长度 2 WORD 可选
21 附加信息 N BYTES 可选
3.5 实时视频播放流程
cmd J9101 ZLMediaKit 车载终端 Jt808EncoderCmd SessionManager JT1078Template REST Controller 前端页面 cmd J9101 ZLMediaKit 车载终端 Jt808EncoderCmd SessionManager JT1078Template REST Controller 前端页面 通道号、音视频类型 码流类型、IP、端口 MsgID: 0x9101 应答 ID: 0x0001 请求实时视频 devId + channelId startLive(devId, J9101) request(cmd, timeout) 创建 SynchronousQueue writeObject(cmd) 组包 (0x9101) 转义+BCC TCP 发送 验证参数 RTMP 推流 rtmp://ip:port/live/devId_channel 通用应答 MsgID: 0x0001 Result: 0/1 解码应答 offer 到队列 返回应答结果 返回播放地址 http-flv / ws-flv 播放器拉流播放
实时音视频传输命令 (0x9101):
java
// J9101.java - 下行命令
public class J9101 extends Rs {
private int channelId; // 逻辑通道号 (1-255)
private int streamType; // 音视频类型 (0-视频,1-音频)
private int bitrateType; // 码流类型 (0-主,1-子)
private String ip; // 服务器 IP
private int tcpPort; // TCP 端口
private int udpPort; // UDP 端口
@Override
public ByteBuf encode() {
ByteBuf buf = Unpooled.buffer();
buf.writeByte(channelId);
buf.writeByte(streamType);
buf.writeByte(bitrateType);
buf.writeByte(0); // 保留
buf.writeBytes(IpUtil.parse(ip)); // IP 转 4 字节
buf.writeShort(tcpPort);
buf.writeShort(udpPort);
return buf;
}
}
3.6 录像回放控制流程
ZLM J9201 J9205 车载终端 SessionManager JT1078Template 前端页面 ZLM J9201 J9205 车载终端 SessionManager JT1078Template 前端页面 第一步:查询录像列表 开始时间、结束时间 通道号、媒体类型 返回文件列表 文件名、大小、时长 第二步:发起回放请求 文件名、IP、端口 开始/结束时间 queryBackTime(J9205) 查询命令 MsgID: 0x9205 检索本地存储 应答 MsgID: 0x1205 startBackLive(J9201) 回放命令 MsgID: 0x9201 读取文件 RTSP 推流 rtsp://ip:port/playback/xxx 应答 MsgID: 0x0001 返回播放地址
3.7 多媒体数据上传流程
SessionManager J0801 Handler 车载终端 SessionManager J0801 Handler 车载终端 媒体类型 (图片/录音) 通道号、事件编码 媒体数据 媒体类型、通道号 事件类型、地理位置 媒体数据 (JPEG 等) opt [大数据分包] 多媒体上传 MsgID: 0x0801 解析 JTMediaEventInfo response(媒体信息) 通知等待方 通用应答 MsgID: 0x0001 分包上传 总包数 + 包序号 重组完整数据
四、上下行消息对照表
4.1 通用类消息
| 消息 ID | 名称 | 方向 | 描述 |
|---|---|---|---|
| 0x0001 | 平台通用应答 | 双向 | 对大多数命令的确认响应 |
| 0x0002 | 终端心跳 | 上行 | 终端定期发送保持连接 |
| 0x0003 | 终端注销 | 上行 | 终端主动断开连接 |
4.2 终端管理类消息
| 消息 ID | 名称 | 方向 | 描述 |
|---|---|---|---|
| 0x0100 | 终端注册 | 上行 | 终端首次接入注册 |
| 0x0102 | 终端鉴权 | 上行 | 终端发送鉴权码验证 |
| 0x8100 | 终端注册应答 | 下行 | 平台返回注册结果 |
| 0x8103 | 查询终端参数 | 下行 | 查询设备配置参数 |
| 0x0104 | 查询终端参数应答 | 上行 | 设备返回参数列表 |
| 0x8104 | 设置终端参数 | 下行 | 配置设备参数 |
4.3 位置服务类消息
| 消息 ID | 名称 | 方向 | 描述 |
|---|---|---|---|
| 0x0200 | 位置信息汇报 | 上行 | 定期上报 GPS 位置 |
| 0x8201 | 位置信息查询 | 下行 | 主动查询当前位置 |
| 0x0201 | 位置信息查询应答 | 上行 | 返回当前位置 |
4.4 车辆控制类消息
| 消息 ID | 名称 | 方向 | 描述 |
|---|---|---|---|
| 0x8500 | 车辆控制 | 下行 | 远程控制车门、断油电等 |
| 0x0500 | 车辆控制应答 | 上行 | 执行结果反馈 |
4.5 电子围栏类消息
| 消息 ID | 名称 | 方向 | 描述 |
|---|---|---|---|
| 0x8600 | 设置圆形区域 | 下行 | 添加圆形电子围栏 |
| 0x8601 | 设置矩形区域 | 下行 | 添加矩形电子围栏 |
| 0x8602 | 设置多边形区域 | 下行 | 添加多边形围栏 |
| 0x8603 | 设置路线 | 下行 | 设置行驶路线 |
| 0x8606 | 查询区域/线路 | 下行 | 查询已设置区域 |
| 0x0608 | 查询区域/线路应答 | 上行 | 返回区域数据 |
4.6 多媒体类消息
| 消息 ID | 名称 | 方向 | 描述 |
|---|---|---|---|
| 0x0801 | 多媒体数据上传 | 上行 | 上传图片/录音文件 |
| 0x8801 | 摄像头立即拍摄 | 下行 | 触发拍照指令 |
| 0x8802 | 存储多媒体检索 | 下行 | 查询存储的文件 |
| 0x0802 | 存储多媒体检索应答 | 上行 | 返回文件列表 |
| 0x8803 | 录音开始 | 下行 | 启动车内录音 |
| 0x1206 | 录音开始应答 | 上行 | 确认录音启动 |
4.7 音视频实时监控类消息
| 消息 ID | 名称 | 方向 | 描述 |
|---|---|---|---|
| 0x9101 | 实时音视频传输 | 下行 | 开启实时视频推流 |
| 0x9102 | 音视频传输结束 | 下行 | 停止视频推流 |
| 0x9201 | 音视频回放 | 下行 | 回放历史录像 |
| 0x9202 | 音视频回放控制 | 下行 | 暂停/继续/拖拽 |
| 0x9205 | 查询音视频资源 | 下行 | 查询可回放资源 |
| 0x1205 | 查询音视频资源应答 | 上行 | 返回资源列表 |
五、核心模块实现
5.1 Netty Pipeline 配置
java
// TcpServer.java
private void startTcpServer() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
public void initChannel(NioSocketChannel ch) {
ch.pipeline()
// 1. 空闲检测 (10 分钟无活动断开)
.addLast(new IdleStateHandler(10, 0, 0, TimeUnit.MINUTES))
// 2. 帧解码 (以 0x7E 为分隔符)
.addLast(new DelimiterBasedFrameDecoder(2048,
Unpooled.wrappedBuffer(new byte[]{0x7e})))
// 3. 协议解码 (转义、校验、解析)
.addLast(new Jt808Decoder(applicationEventPublisher, service))
// 4. 协议编码 (组包、转义、校验)
.addLast(new Jt808Encoder())
.addLast(new Jt808EncoderCmd())
// 5. 业务处理 (连接事件、异常处理)
.addLast(new Jt808Handler(applicationEventPublisher));
}
});
bootstrap.bind(port).sync().channel().closeFuture().sync();
}
5.2 解码器实现
java
// Jt808Decoder.java
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
Session session = ctx.channel().attr(Session.KEY).get();
// 1. 转义处理和校验码验证
ByteBuf buf = unEscapeAndCheck(in);
// 2. 解析消息头
Header header = new Header();
header.setMsgId(buf.readSlice(2).toString()); // 消息 ID
header.setMsgPro(buf.readUnsignedShort()); // 消息属性
// 3. 判断是否分包
boolean isSubpackage = (header.getMsgPro() >>> 13 & 1) == 1;
// 4. 读取终端手机号 (根据版本)
if (header.is2019Version()) {
header.setVersion(buf.readUnsignedByte());
String devId = ByteBufUtil.hexDump(buf.readSlice(10));
header.setPhoneNumber(devId.replaceFirst("^0*", ""));
} else {
header.setPhoneNumber(ByteBufUtil.hexDump(buf.readSlice(6))
.replaceFirst("^0*", ""));
}
// 5. 读取流水号
header.setSn(buf.readUnsignedShort());
// 6. 处理分包
if (isSubpackage) {
int packageCount = buf.readUnsignedShort();
int packageNumber = buf.readUnsignedShort();
ByteBuf intactBuf = MultiPacketManager.INSTANCE
.add(header, packageCount, buf);
if (intactBuf == null) return; // 包未收完
buf = intactBuf;
}
// 7. 获取对应的处理器
Re handler = CodecFactory.getHandler(header.getMsgId());
// 8. 调用处理器解码
Rs decode = handler.decode(buf, header, session, service);
// 9. 发布事件
ApplicationEvent event = handler.getEvent();
if (event != null) {
applicationEventPublisher.publishEvent(event);
}
// 10. 传递应答到 outbound
if (decode != null) {
out.add(decode);
}
}
5.3 编码器实现
java
// Jt808EncoderCmd.java
@Override
protected void encode(ChannelHandlerContext ctx, Cmd cmd, ByteBuf out) {
Session session = ctx.channel().attr(Session.KEY).get();
Rs msg = cmd.getRs();
// 1. 编码消息体
List<ByteBuf> encodeList = encode(msg, session, cmd.getPackageNo());
// 2. 写入输出
for (ByteBuf byteBuf : encodeList) {
out.writeBytes(byteBuf);
}
}
// 构建单个消息包
private static ByteBuf buildMsgByte(Header header, String id, Session session,
Integer packageNo, ByteBuf encode,
Integer packetIndex, Integer packetTotal) {
ByteBuf byteBuf = Unpooled.buffer();
// 1. 写入消息 ID
byteBuf.writeBytes(ByteBufUtil.decodeHexDump(id));
// 2. 写入消息属性 (含长度和分包标志)
if (header.is2019Version()) {
int msgBody = encode.readableBytes() | 1 << 14;
if (packetIndex > 0) {
msgBody = msgBody | 1 << 13; // 分包标志
}
byteBuf.writeShort(msgBody);
byteBuf.writeByte(header.getVersion()); // 版本号
byteBuf.writeBytes(phoneToBytes(header.getPhoneNumber())); // 手机号
} else {
byteBuf.writeShort(encode.readableBytes());
byteBuf.writeBytes(phoneToBytes(header.getPhoneNumber()));
}
// 3. 写入流水号
byteBuf.writeShort(packageNo);
// 4. 写入分包信息 (如果有)
if (packetIndex > 0) {
byteBuf.writeShort(packetTotal);
byteBuf.writeShort(packetIndex);
}
// 5. 写入消息体
byteBuf.writeBytes(encode);
// 6. 添加校验码
sign(byteBuf);
// 7. 转义处理
byteBuf = escapeAndCheck0(byteBuf);
return byteBuf;
}
5.4 会话管理器
java
// SessionManager.java
public enum SessionManager {
INSTANCE;
// 存储所有会话
private Map<String, Session> sessionMap = new ConcurrentHashMap<>();
// 存储请求 - 应答等待队列
private Map<String, SynchronousQueue<Object>> topicSubscribers
= new ConcurrentHashMap<>();
/**
* 创建新会话
*/
public Session newSession(Channel channel) {
return new Session(channel);
}
/**
* 同步请求 (阻塞等待应答)
*/
public Object request(Cmd cmd, int timeOut) {
Session session = this.get(cmd.getPhoneNumber());
if (session == null) {
log.error("DevId: {} not online!", cmd.getPhoneNumber());
return null;
}
// 生成唯一请求 key
String requestKey = requestKey(cmd.getPhoneNumber(),
cmd.getRespId(),
cmd.getPackageNo());
// 创建等待队列
SynchronousQueue<Object> subscribe = subscribe(requestKey);
// 发送命令
session.writeObject(cmd);
// 阻塞等待应答
try {
return subscribe.poll(timeOut, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Timeout waiting response", e);
} finally {
this.unsubscribe(requestKey);
}
return null;
}
/**
* 接收应答数据并唤醒等待线程
*/
public boolean response(String devId, String respId,
Long responseNo, Object data) {
String requestKey = requestKey(devId, respId, responseNo);
boolean result = false;
if (responseNo == null) {
// 模糊匹配 (用于无流水号的应答)
for (String key : topicSubscribers.keySet()) {
if (key.startsWith(requestKey)) {
SynchronousQueue<Object> queue = topicSubscribers.get(key);
if (queue != null) {
result = true;
queue.offer(data, 2, TimeUnit.SECONDS);
}
}
}
} else {
// 精确匹配
SynchronousQueue<Object> queue = topicSubscribers.get(requestKey);
if (queue != null) {
result = true;
queue.offer(data, 2, TimeUnit.SECONDS);
}
}
if (!result) {
log.warn("Not find response, key:{} data:{}", requestKey, data);
}
return result;
}
}
六、数据包示例
6.1 终端注册数据包
上行 (0x0100):
原始数据 (16 进制):
7E 01 00 00 3F 31 39 31 32 33 34 35 36 37 38 00 01
00 05 41 42 43 44 45 30 30 30 30 31 5A 58 36 30 31
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 31
32 33 34 35 36 37 01 E6 B5 99 41 31 32 33 34 35 7E
解析:
- 帧头:7E
- 消息 ID: 01 00 (终端注册)
- 消息属性:00 3F (消息体长度 63 字节)
- 手机号:31 39 31 32 33 34 35 36 37 38 (91912345678)
- 流水号:00 01
- 省份 ID: 00 05
- 城市 ID: 00 01
- 制造商 ID: 41 42 43 44 45 (ABCDE)
- 终端型号:30 30 30 30 31 5A 58 36 30 31... (00001ZX601...)
- 终端 ID: 31 32 33 34 35 36 37 (1234567)
- 车牌颜色:01 (蓝色)
- 车牌号:E6 B5 99 41 31 32 33 34 35 (浙 A12345)
- 校验码:7E
- 帧尾:7E
下行应答 (0x8100):
原始数据:
7E 81 00 00 07 31 39 31 32 33 34 35 36 37 38 00 01
00 38 39 30 31 32 66 33 61 2D 34 62 35 63 2D 37 64
38 65 2D 39 66 30 61 2D 31 62 32 63 33 64 34 65 35
66 36 37 38 39 30 7E
解析:
- 消息 ID: 81 00 (终端注册应答)
- 消息属性:00 07 (消息体长度 7 字节)
- 应答流水号:00 01
- 应答结果:00 (成功)
- 鉴权码:38 39 30 31 32 66 33 61... (89012f3a-4b5c-7d8e-9f0a-1b2c3d4e5f67890)
6.2 位置信息上报数据包
上行 (0x0200):
原始数据:
7E 02 00 00 1C 31 39 31 32 33 34 35 36 37 38 00 02
00 00 0B 91 4E 20 00 75 31 CD 00 05 00 1E 07 15 10
30 30 01 0C 31 32 33 34 35 36 37 38 39 41 42 43 44
EF 7E
解析:
- 消息 ID: 02 00 (位置信息汇报)
- 消息体长度:1C (28 字节)
- 纬度:00 0B 91 4E (758062 = 北纬 25.123456 度)
- 经度:00 75 31 CD (7675341 = 东经 121.234567 度)
- 高程:00 05 (5 米)
- 速度:00 1E (30 = 3.0 km/h)
- 方向:07 (126 度)
- 时间:15 10 30 30 30 31 (21-10-30 10:00:01)
- 附加信息:0C 31 32 33... (可选)
七、常见问题与解决方案
7.1 设备注册失败
问题现象: 设备反复注册,始终显示失败
可能原因:
- 数据库中不存在该设备手机号
- 车牌号格式不正确 (GBK 编码问题)
- 制造商 ID 不是 5 位大写字母
解决方案:
sql
-- 在数据库中预授权设备
INSERT INTO jt_device (phone_number, plate_no, status)
VALUES ('91912345678', '浙 A12345', 1);
7.2 鉴权码不匹配
问题现象: 注册成功但鉴权失败
排查步骤:
- 检查 Session 是否正确保存鉴权码
- 确认设备发送的鉴权码格式 (UTF-8 vs GBK)
- 查看日志中的鉴权码比对
日志示例:
[JT-注册成功] 设备:JTDevice{phoneNumber='91912345678', authCode='89012f3a...'}
设备鉴权:authenticationCode: 89012f3a...
[JT-鉴权成功] 设备:91912345678
7.3 心跳超时断开
问题现象: 设备频繁掉线
调整心跳参数:
yaml
# application.yml
jt1078:
heartbeat-interval: 60 # 心跳间隔 (秒)
idle-timeout: 600 # 空闲超时 (秒)
7.4 大包分包问题
问题现象: 图片/文件上传失败
调试方法:
java
// Jt808Decoder.java
if (isSubpackage) {
log.debug("[分包消息] header: {}, 序号:{}, 总数:{}",
header, packageNumber, packageCount);
ByteBuf intactBuf = MultiPacketManager.INSTANCE
.add(header, packageCount, buf);
if (intactBuf == null) {
log.info("等待分包完成...");
return;
}
buf = intactBuf;
}
八、开发调试建议
8.1 日志级别配置
yaml
logging:
level:
com.genersoft.iot.vmp.jt1078: DEBUG
io.netty: INFO
8.2 使用测试工具
项目提供了测试类 JT1078ServerTest.java:
java
public class JT1078ServerTest {
public static void main(String[] args) {
TcpServer tcpServer = new TcpServer(21078, null, null);
tcpServer.start();
System.out.println("Start jt1078 server success!");
// 模拟下发指令
Scanner s = new Scanner(System.in);
while (true) {
String code = s.nextLine();
// 测试各种指令
}
}
}
8.3 抓包分析
使用 Wireshark 或 tcpdump 抓取原始报文:
bash
# Linux
tcpdump -i any port 21078 -w jt1078.pcap
# Windows (PowerShell)
netsh trace start capture=yes IPv4.Address=any ETW=off TraceFile=C:\jt1078.etl
九、性能优化建议
9.1 内存池化
java
// 使用 Netty 内存池
ByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
ByteBuf buf = allocator.buffer(1024);
9.2 异步化处理
java
// 业务处理放入独立线程池
ExecutorService businessExecutor = Executors.newFixedThreadPool(10);
@Override
protected void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof Rs) {
businessExecutor.submit(() -> {
// 耗时业务处理
});
}
}
9.3 Redis 缓存位置
java
// 位置信息不每次都写数据库
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void updateDevicePosition(String phoneNumber, double lon, double lat) {
String key = "jt:position:" + phoneNumber;
redisTemplate.opsForValue().set(key, lon + "," + lat, 300, TimeUnit.SECONDS);
// 每 10 次上报写一次数据库
if (counter.incrementAndGet() % 10 == 0) {
deviceMapper.updatePosition(phoneNumber, lon, lat);
}
}
十、总结
JT/T 1078 协议作为车载视频监控的核心通信协议,具有以下特点:
✅ 标准化程度高 : 严格遵循部标格式,各字段定义清晰
✅ 扩展性强 : 基于 JT/T 808 扩展,兼容性好
✅ 实时性好 : TCP 长连接保证指令及时送达
✅ 可靠性高 : 完善的应答机制和心跳保活
✅ 安全性好: 鉴权码验证防止非法接入
掌握该协议对于从事车联网、视频监控相关开发具有重要意义!
文档版本: v1.0
最后更新: 2026-03-11
参考文档:
- JT/T 808-2013 道路运输车辆卫星定位系统终端通讯协议
- JT/T 808-2019 道路运输车辆卫星定位系统终端通讯协议
- JT/T 1078-2016 道路运输车辆卫星定位系统车载视频终端通信协议