最近在项目中遇到对接JT808和JT809,其中遇到很多问题,首先因为对这两个协议不熟悉,准备在网上搜资料查询,有没有开源的关于JT808和809协议的项目,发现JT808的已经有开源的,而对于JT809的好像没有。经过两天自己查询资料,然后说下JT809,首先,不管是对接别人的数据还是接受下级的数据,都需要自己去按照官方定义的协议。先说下客户端,选择Netty3.10.6,以下是pom.xml配置文件
4.0.0
jt809client
jt809clientdemol
1.0-SNAPSHOT
org.slf4j
slf4j-api
1.7.24
ch.qos.logback
logback-classic
1.0.13
org.apache.commons
commons-lang3
3.6
以下是客户端的代码:
TcpClient.java
package netty.client;
import handler.Decoder;
import handler.HeartBeatHandler;
import handler.RecevieHandler;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.jboss.netty.handler.timeout.IdleStateHandler;
import org.jboss.netty.util.HashedWheelTimer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class TcpClient {
private static final Logger LOG = LoggerFactory.getLogger(TcpClient.class);
private static final int DEFAULT_PORT = 8001;
private long connectTimeoutMillis = 3000;
private int port = DEFAULT_PORT;
private boolean tcpNoDelay = false;
private boolean reuseAddress = true;
private boolean keepAlive = true;
private int workerCount = 4;
private ClientBootstrap bootstrap = null;
private static Channel channel = null;
private Executor bossExecutor = Executors.newCachedThreadPool();
private Executor workerExecutor = Executors.newCachedThreadPool();
private static TcpClient instance = new TcpClient();
private TcpClient() {
init();
}
public static TcpClient getInstence() {
return instance;
}
public void init() {
bootstrap = new ClientBootstrap(new NioClientSocketChannelFactory(
bossExecutor, workerExecutor, workerCount));
bootstrap.setOption("tcpNoDelay", tcpNoDelay);
bootstrap.setOption("connectTimeoutMillis", connectTimeoutMillis);
bootstrap.setOption("reuseAddress", reuseAddress);
bootstrap.setOption("keepAlive", keepAlive);
}
public Channel getChannel(String address, int port) {
if (null == channel || !channel.isOpen()) {
bootstrap.setOption("writeBufferHighWaterMark", 64 * 1024);
bootstrap.setOption("writeBufferLowWaterMark", 32 * 1024);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
//@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
// pipeline.addLast("loging", new LoggingHandler(InternalLogLevel.ERROR)); 打印日志信息,上线稳定后可去掉
pipeline.addLast("timeout", new IdleStateHandler(new HashedWheelTimer(), 10, 60, 0));//设置空闲心跳机制
pipeline.addLast("heartbeat", new HeartBeatHandler());//心跳发送包处理handler
pipeline.addLast("decode", new Decoder());//解码
pipeline.addLast("loginHandler", new RecevieHandler());//反馈数据处理
return pipeline;
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress(
address, port));
future.awaitUninterruptibly();
if (future.isSuccess()) {
channel = future.getChannel();
} else {
//throw new Exception(future.getCause());
}
}
return channel;
}
public long getConnectTimeoutMillis() {
return connectTimeoutMillis;
}
public void setConnectTimeoutMillis(long connectTimeoutMillis) {
this.connectTimeoutMillis = connectTimeoutMillis;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isTcpNoDelay() {
return tcpNoDelay;
}
public void setTcpNoDelay(boolean tcpNoDelay) {
this.tcpNoDelay = tcpNoDelay;
}
public boolean isReuseAddress() {
return reuseAddress;
}
public void setReuseAddress(boolean reuseAddress) {
this.reuseAddress = reuseAddress;
}
public boolean isKeepAlive() {
return keepAlive;
}
public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}
public int getWorkerCount() {
return workerCount;
}
public void setWorkerCount(int workerCount) {
this.workerCount = workerCount;
}
}
TcpClient809.java
package netty.client;
import bean.Idc2AwsGpsVo;
import bean.Message;
import org.apache.commons.lang3.StringUtils;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utill.CRC16CCITT;
import utill.Constants;
import utill.JT809Constants;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.Date;
public class TcpClient809 {
private static Logger LOG = LoggerFactory.getLogger(TcpClient809.class);
public static int PLANT_CODE;//公司介入码
public static int ZUCHE_ID_FUJIAN;//公司用户名
public static String ZUCHE_PWD_FUJIAN;
public static String LONGINSTATUS = "";
public static String LOGINING = "logining";
private static int LOGIN_FLAG = 0;
private static String DOWN_LINK_IP = "127.0.0.1";//初始化基类
private static TcpClient tcpClient = TcpClient.getInstence();//初始化
private static TcpClient809 tcpClient809 = new TcpClient809();
//初始化channel,以便心跳机制及时登录
private Channel channel = tcpClient.getChannel(Constants.TCP_ADDRESS, Constants.TCP_PORT);
public static TcpClient809 getInstance() {
String localIp = "127.0.0.1";
if (StringUtils.isNotBlank(localIp)) {
PLANT_CODE = 11;
ZUCHE_ID_FUJIAN = 11;
ZUCHE_PWD_FUJIAN = "";
} else {
LOG.error("获取本机IP异常");
}
return tcpClient809;
}
/**
* 判断是否登录 * boolean * @return
*/
public boolean isLogined() {
return Constants.LOGIN_STATAUS.equals(LONGINSTATUS); //Constants常量类,自己随便定义就好,LOGIN_STATAUS="0x00"
}
/**
* 登录接入平台 * boolean * @return
*/
public boolean login2FuJianGov() {
boolean success = false;
if (!Constants.LOGIN_STATAUS.equals(LONGINSTATUS) && !LOGINING.equals(LONGINSTATUS)) {
//开始登录 Message为数据对象,代码稍后给出
Message msg = new Message(JT809Constants.UP_CONNECT_REQ);
ChannelBuffer buffer = ChannelBuffers.buffer(46);
buffer.writeInt(ZUCHE_ID_FUJIAN);
byte[] pwd = getBytesWithLengthAfter(8, ZUCHE_PWD_FUJIAN.getBytes());
buffer.writeBytes(pwd);
byte[] ip = getBytesWithLengthAfter(32, DOWN_LINK_IP.getBytes());
buffer.writeBytes(ip);
buffer.writeShort((short) 8091);//不明白这是什么
msg.setMsgBody(buffer);
channel = tcpClient.getChannel(Constants.TCP_ADDRESS, Constants.TCP_PORT);
channel.write(buildMessage(msg));
LONGINSTATUS = LOGINING;
}
return success;
}
public static ChannelBuffer buildMessage(Message msg) {
int bodyLength = 0;
if (null != msg.getMsgBody()) {
bodyLength = msg.getMsgBody().readableBytes();
}
msg.setMsgGesscenterId(PLANT_CODE);
ChannelBuffer buffer = ChannelBuffers.dynamicBuffer(bodyLength + Message.MSG_FIX_LENGTH);
ChannelBuffer headBuffer = ChannelBuffers.buffer(22);//---数据头
headBuffer.writeInt(buffer.capacity() - 1);
headBuffer.writeInt(msg.getMsgSn());
headBuffer.writeShort((short) msg.getMsgId());
headBuffer.writeInt(msg.getMsgGesscenterId());
headBuffer.writeBytes(msg.getVersionFlag());
headBuffer.writeByte(0);
headBuffer.writeInt(10);
buffer.writeBytes(headBuffer);//---数据体
if (null != msg.getMsgBody()) {
buffer.writeBytes(msg.getMsgBody());
}
ChannelBuffer finalBuffer = ChannelBuffers.copiedBuffer(buffer);//--crc校验码
byte[] b = ChannelBuffers.buffer(finalBuffer.readableBytes()).array();
finalBuffer.getBytes(0, b);
int crcValue = CRC16CCITT.crc16(b);
finalBuffer.writeShort((short) crcValue);//2//转义
byte[] bytes = ChannelBuffers.copiedBuffer(finalBuffer).array();
ChannelBuffer headFormatedBuffer = ChannelBuffers.dynamicBuffer(finalBuffer.readableBytes());
formatBuffer(bytes, headFormatedBuffer);
ChannelBuffer buffera = ChannelBuffers.buffer(headFormatedBuffer.readableBytes() + 2);
buffera.writeByte(Message.MSG_HEAD);
buffera.writeBytes(headFormatedBuffer);
buffera.writeByte(Message.MSG_TALL);
return ChannelBuffers.copiedBuffer(buffera);
}
/**
* 发送数据到接入平台 * boolean * @param awsVo 是上层程序得到的带发送的数据对象,可以看自己的需求,替换 * @return
*/
public boolean sendMsg2FuJianGov(Idc2AwsGpsVo awsVo) {
boolean success = false;
if (isLogined()) {
//已经登录成功,开始发送数据
LOG.info("开始发送数据");
channel = tcpClient.getChannel(Constants.TCP_ADDRESS, Constants.TCP_PORT);
if (null != channel && channel.isWritable()) {
Message msg = buildSendVO(awsVo);
ChannelBuffer msgBuffer = buildMessage(msg);
channel.write(msgBuffer);
} else {
LONGINSTATUS = "";
}
} else if (
LOGIN_FLAG == 0) {
LOGIN_FLAG++;
login2FuJianGov();
LOG.error("--------------第一次登录");
} else {
LOG.error("--------------等待登录");
}
return success;
}
/**
* 转换VO * void * @param awsVo
*/
private Message buildSendVO(Idc2AwsGpsVo awsVo) {
Message msg = new Message(JT809Constants.UP_EXG_MSG);
ChannelBuffer buffer = ChannelBuffers.buffer(36);//是否加密
buffer.writeByte((byte) 0);//0未加密 // 1//日月年dmyy
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
buffer.writeByte((byte) cal.get(Calendar.DATE));
buffer.writeByte((byte) (cal.get(Calendar.MONTH) + 1));
String hexYear = "0" + Integer.toHexString(cal.get(Calendar.YEAR));
buffer.writeBytes(hexStringToByte(hexYear));//4//时分秒
buffer.writeByte((byte) cal.get(Calendar.HOUR));
buffer.writeByte((byte) cal.get(Calendar.MINUTE));
buffer.writeByte((byte) cal.get(Calendar.SECOND));//3//经度,纬度
buffer.writeInt(formatLonLat(awsVo.getLon()));//4
buffer.writeInt(formatLonLat(awsVo.getLat()));//4//速度
buffer.writeShort(awsVo.getSpeed());//2//行驶记录速度
buffer.writeShort(awsVo.getSpeed());//2//车辆当前总里程数
buffer.writeInt(awsVo.getMileage());//4//方向
buffer.writeShort(awsVo.getDirection());//2//海拔
buffer.writeShort((short) 0);//2//车辆状态buffer.writeInt(1);//4//报警状态
buffer.writeInt(0);//0表示正常;1表示报警//4
ChannelBuffer headBuffer = ChannelBuffers.buffer(buffer.capacity() + 28);
headBuffer.writeBytes(getBytesWithLengthAfter(21, awsVo.getVehicleNo().getBytes()));//21 车牌号
headBuffer.writeByte((byte) 1);//1 车牌颜色:注意不是车身颜色
headBuffer.writeShort(JT809Constants.UP_EXG_MSG_REAL_LOCATION);//2 子业务码
headBuffer.writeInt(buffer.capacity());
headBuffer.writeBytes(buffer);
msg.setMsgBody(headBuffer);
return msg;
}
/**
* 报文转义 * void * @param bytes * @param formatBuffer
*/
private static void formatBuffer(byte[] bytes, ChannelBuffer formatBuffer) {
for (byte b : bytes) {
switch (b) {
case 0x5b:
byte[] formatByte0x5b = new byte[2];
formatByte0x5b[0] = 0x5a;
formatByte0x5b[1] = 0x01;
formatBuffer.writeBytes(formatByte0x5b);
break;
case 0x5a:
byte[] formatByte0x5a = new byte[2];
formatByte0x5a[0] = 0x5a;
formatByte0x5a[1] = 0x02;
formatBuffer.writeBytes(formatByte0x5a);
break;
case 0x5d:
byte[] formatByte0x5d = new byte[2];
formatByte0x5d[0] = 0x5e;
formatByte0x5d[1] = 0x01;
formatBuffer.writeBytes(formatByte0x5d);
break;
case 0x5e:
byte[] formatByte0x5e = new byte[2];
formatByte0x5e[0] = 0x5e;
formatByte0x5e[1] = 0x02;
formatBuffer.writeBytes(formatByte0x5e);
break;
default:
formatBuffer.writeByte(b);
break;
}
}
}
/**
* 16进制字符串转换成byte数组 * byte[] * @param hex
*/
public static byte[] hexStringToByte(String hex) {
hex = hex.toUpperCase();
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
private static byte toByte(char c) {
byte b = (byte) "0123456789ABCDEF".indexOf(c);
return b;
}
/**
* 格式化经纬度,保留六位小数 * int * @param needFormat * @return
*/
private int formatLonLat(Double needFormat) {
NumberFormat numFormat = NumberFormat.getInstance();
numFormat.setMaximumFractionDigits(6);
numFormat.setGroupingUsed(false);
String fristFromat = numFormat.format(needFormat);
Double formatedDouble = Double.parseDouble(fristFromat);
numFormat.setMaximumFractionDigits(0);
String formatedValue = numFormat.format(formatedDouble * 1000000);
return Integer.parseInt(formatedValue);
}
/**
* 补全位数不够的定长参数 有些定长参数,实际值长度不够,在后面补0x00 * byte[] * @param length * @param pwdByte * @return
*/
private byte[] getBytesWithLengthAfter(int length, byte[] pwdByte) {
byte[] lengthByte = new byte[length];
for (int i = 0; i < pwdByte.length; i++) {
lengthByte[i] = pwdByte[i];
}
for (int i = 0; i < (length - pwdByte.length); i++) {
lengthByte[pwdByte.length + i] = 0x00;
}
return lengthByte;
}
public static void main(String[] args) {
TcpClient809 s = TcpClient809.getInstance();
Idc2AwsGpsVo awsVo = new Idc2AwsGpsVo();
awsVo.setDirection((short) 12);
awsVo.setLon(117.2900911);
awsVo.setLat(39.56362);
awsVo.setSpeed((short) 45);
awsVo.setMileage(10001);
awsVo.setVehicleNo("幽123D32");
LOG.info("开始send message");
s.sendMsg2FuJianGov(awsVo);
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
s.sendMsg2FuJianGov(awsVo);
}
}
上述是客户端登陆,模拟下级平台的登陆,心跳包,还有就是上报车子的GPS信息到上级平台。
以下地址是csdn下载连接地址。
https://download.csdn.net/download/yanchangyufan/10567788