├── 测试数据.txt ├── 调试工具.zip ├── README.md └── jt808-tcp-netty ├── src └── main │ ├── java │ └── cn │ │ └── hylexus │ │ └── jt808 │ │ ├── common │ │ ├── MyClassificationLogAppender.java │ │ └── TPMSConsts.java │ │ ├── client │ │ ├── ClientHandler.java │ │ ├── test2.java │ │ └── Client.java │ │ ├── vo │ │ ├── req │ │ │ ├── TerminalAuthenticationMsg.java │ │ │ ├── LocationInfoUploadMsg.java │ │ │ └── TerminalRegisterMsg.java │ │ ├── resp │ │ │ ├── TerminalRegisterMsgRespBody.java │ │ │ └── ServerCommonRespMsgBody.java │ │ ├── Session.java │ │ └── PackageData.java │ │ ├── service │ │ ├── codec │ │ │ ├── Decoder4LoggingOnly.java │ │ │ ├── MsgEncoder.java │ │ │ └── MsgDecoder.java │ │ ├── BaseMsgProcessService.java │ │ ├── TerminalMsgProcessService.java │ │ └── handler │ │ │ └── TCPServerHandler.java │ │ ├── util │ │ ├── HexStringUtils.java │ │ ├── BCD8421Operater.java │ │ ├── JT808ProtocolUtils.java │ │ └── BitOperator.java │ │ └── server │ │ ├── SessionManager.java │ │ └── TCPServer.java │ └── resources │ ├── log4j.xml │ └── log4j.dtd ├── .gitignore └── pom.xml /测试数据.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunhun1122/jt808/HEAD/测试数据.txt -------------------------------------------------------------------------------- /调试工具.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunhun1122/jt808/HEAD/调试工具.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jt808 2 | 北斗部标808协议解析 3 | 4 | 5 | # maven项目 这个是某个哥们写的,下载后修改了点内容。现在能跑起来了 6 | 7 | #启动: 8 | TCPServer.java 启动netty。 9 | 10 | #模拟工具 tcp客户端模拟发送数据 11 | 12 | 调试注意使用 十六进制 发送数据 13 | 14 | 15 | #其他自己百度吧 16 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/common/MyClassificationLogAppender.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.common; 2 | 3 | import org.apache.log4j.DailyRollingFileAppender; 4 | import org.apache.log4j.Priority; 5 | 6 | public class MyClassificationLogAppender extends DailyRollingFileAppender { 7 | 8 | @Override 9 | public boolean isAsSevereAsThreshold(Priority priority) { 10 | return this.getThreshold().equals(priority); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/client/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.client; 2 | 3 | import io.netty.channel.ChannelHandlerAdapter; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.util.ReferenceCountUtil; 6 | 7 | public class ClientHandler extends ChannelHandlerAdapter{ 8 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 9 | try{ 10 | //接收服务端返回的数据 11 | String data = (String)msg; 12 | System.out.println("Client接收到的数据:"+data); 13 | }finally{ 14 | //因为没有进行写操作,所以需要自己来释放 15 | ReferenceCountUtil.release(msg); 16 | } 17 | } 18 | 19 | @Override 20 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 21 | cause.printStackTrace(); 22 | ctx.close(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/client/test2.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.client; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import java.io.PrintWriter; 7 | import java.net.Socket; 8 | import java.net.UnknownHostException; 9 | 10 | 11 | 12 | public class test2 { 13 | 14 | public static void main(String[] args) throws UnknownHostException, IOException { 15 | //1.创建客户端Socket,指定服务器地址和端口 16 | Socket so=new Socket("localhost", 20048);//端口号要和服务器端相同 17 | //2.获取输出流,向服务器端发送登录的信息 18 | OutputStream os=so.getOutputStream();//字节输出流 19 | PrintWriter pw=new PrintWriter(os);//字符输出流 20 | BufferedWriter bw=new BufferedWriter(pw);//加上缓冲流 21 | bw.write("7E0200003801328226177202C300000000200C00C30245427806BCC868007A02E4001F18112710100301040007D300030202DA2504000000002B040021000030011931010F1F7E"); 22 | bw.flush(); 23 | so.shutdownOutput();//关闭输出流 24 | //3.关闭资源 25 | // bw.close(); 26 | // pw.close(); 27 | // os.close(); 28 | // so.close(); 29 | 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/vo/req/TerminalAuthenticationMsg.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.vo.req; 2 | 3 | import java.util.Arrays; 4 | 5 | import cn.hylexus.jt808.common.TPMSConsts; 6 | import cn.hylexus.jt808.vo.PackageData; 7 | 8 | /** 9 | * 终端鉴权消息 10 | * 11 | * @author hylexus 12 | * 13 | */ 14 | public class TerminalAuthenticationMsg extends PackageData { 15 | private String authCode; 16 | 17 | public TerminalAuthenticationMsg() { 18 | } 19 | 20 | public TerminalAuthenticationMsg(PackageData packageData) { 21 | this(); 22 | this.channel = packageData.getChannel(); 23 | this.checkSum = packageData.getCheckSum(); 24 | this.msgBodyBytes = packageData.getMsgBodyBytes(); 25 | this.msgHeader = packageData.getMsgHeader(); 26 | this.authCode = new String(packageData.getMsgBodyBytes(), TPMSConsts.string_charset); 27 | } 28 | 29 | public void setAuthCode(String authCode) { 30 | this.authCode = authCode; 31 | } 32 | 33 | public String getAuthCode() { 34 | return authCode; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "TerminalAuthenticationMsg [authCode=" + authCode + ", msgHeader=" + msgHeader + ", msgBodyBytes=" 40 | + Arrays.toString(msgBodyBytes) + ", checkSum=" + checkSum + ", channel=" + channel + "]"; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/service/codec/Decoder4LoggingOnly.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.service.codec; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import cn.hylexus.jt808.util.HexStringUtils; 9 | import io.netty.buffer.ByteBuf; 10 | import io.netty.buffer.Unpooled; 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.handler.codec.ByteToMessageDecoder; 13 | 14 | /** 15 | * 该解码器只是为了自己日志所用,没其他作用.
16 | * 最终删除 17 | * 18 | * @author hylexus 19 | * 20 | */ 21 | public class Decoder4LoggingOnly extends ByteToMessageDecoder { 22 | 23 | private final Logger log = LoggerFactory.getLogger(getClass()); 24 | private final Logger weblog = LoggerFactory.getLogger("weblog"); 25 | 26 | @Override 27 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 28 | String hex = buf2Str(in); 29 | log.info("ip={},hex = {}", ctx.channel().remoteAddress(), hex); 30 | weblog.info("ip={},hex = {}", ctx.channel().remoteAddress(), hex); 31 | 32 | ByteBuf buf = Unpooled.buffer(); 33 | while (in.isReadable()) { 34 | buf.writeByte(in.readByte()); 35 | } 36 | out.add(buf); 37 | } 38 | 39 | private String buf2Str(ByteBuf in) { 40 | byte[] dst = new byte[in.readableBytes()]; 41 | in.getBytes(0, dst); 42 | return HexStringUtils.toHexString(dst); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/common/TPMSConsts.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.common; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | public class TPMSConsts { 6 | 7 | public static final String string_encoding = "GBK"; 8 | 9 | public static final Charset string_charset = Charset.forName(string_encoding); 10 | // 标识位 11 | public static final int pkg_delimiter = 0x7e; 12 | // 客户端发呆15分钟后,服务器主动断开连接 13 | public static int tcp_client_idle_minutes = 30; 14 | 15 | // 终端通用应答 16 | public static final int msg_id_terminal_common_resp = 0x0001; 17 | // 终端心跳 18 | public static final int msg_id_terminal_heart_beat = 0x0002; 19 | // 终端注册 20 | public static final int msg_id_terminal_register = 0x0100; 21 | // 终端注销 22 | public static final int msg_id_terminal_log_out = 0x0003; 23 | // 终端鉴权 24 | public static final int msg_id_terminal_authentication = 0x0102; 25 | // 位置信息汇报 26 | public static final int msg_id_terminal_location_info_upload = 0x0200; 27 | // 胎压数据透传 28 | public static final int msg_id_terminal_transmission_tyre_pressure = 0x0600; 29 | // 查询终端参数应答 30 | public static final int msg_id_terminal_param_query_resp = 0x0104; 31 | 32 | // 平台通用应答 33 | public static final int cmd_common_resp = 0x8001; 34 | // 终端注册应答 35 | public static final int cmd_terminal_register_resp = 0x8100; 36 | // 设置终端参数 37 | public static final int cmd_terminal_param_settings = 0X8103; 38 | // 查询终端参数 39 | public static final int cmd_terminal_param_query = 0x8104; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/vo/resp/TerminalRegisterMsgRespBody.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.vo.resp; 2 | 3 | public class TerminalRegisterMsgRespBody { 4 | 5 | public static final byte success = 0; 6 | public static final byte car_already_registered = 1; 7 | public static final byte car_not_found = 2; 8 | public static final byte terminal_already_registered = 3; 9 | public static final byte terminal_not_found = 4; 10 | // byte[0-1] 应答流水号(WORD) 对应的终端注册消息的流水号 11 | private int replyFlowId; 12 | /*** 13 | * byte[2] 结果(BYTE)
14 | * 0:成功
15 | * 1:车辆已被注册
16 | * 2:数据库中无该车辆
17 | **/ 18 | private byte replyCode; 19 | // byte[3-x] 鉴权码(STRING) 只有在成功后才有该字段 20 | private String replyToken; 21 | 22 | public TerminalRegisterMsgRespBody() { 23 | } 24 | 25 | public int getReplyFlowId() { 26 | return replyFlowId; 27 | } 28 | 29 | public void setReplyFlowId(int flowId) { 30 | this.replyFlowId = flowId; 31 | } 32 | 33 | public byte getReplyCode() { 34 | return replyCode; 35 | } 36 | 37 | public void setReplyCode(byte code) { 38 | this.replyCode = code; 39 | } 40 | 41 | public String getReplyToken() { 42 | return replyToken; 43 | } 44 | 45 | public void setReplyToken(String token) { 46 | this.replyToken = token; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "TerminalRegisterMsgResp [replyFlowId=" + replyFlowId + ", replyCode=" + replyCode + ", replyToken=" 52 | + replyToken + "]"; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/vo/resp/ServerCommonRespMsgBody.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.vo.resp; 2 | 3 | public class ServerCommonRespMsgBody { 4 | 5 | public static final byte success = 0; 6 | public static final byte failure = 1; 7 | public static final byte msg_error = 2; 8 | public static final byte unsupported = 3; 9 | public static final byte warnning_msg_ack = 4; 10 | 11 | // byte[0-1] 应答流水号 对应的终端消息的流水号 12 | private int replyFlowId; 13 | // byte[2-3] 应答ID 对应的终端消息的ID 14 | private int replyId; 15 | /** 16 | * 0:成功∕确认
17 | * 1:失败
18 | * 2:消息有误
19 | * 3:不支持
20 | * 4:报警处理确认
21 | */ 22 | private byte replyCode; 23 | 24 | public ServerCommonRespMsgBody() { 25 | } 26 | 27 | public ServerCommonRespMsgBody(int replyFlowId, int replyId, byte replyCode) { 28 | super(); 29 | this.replyFlowId = replyFlowId; 30 | this.replyId = replyId; 31 | this.replyCode = replyCode; 32 | } 33 | 34 | public int getReplyFlowId() { 35 | return replyFlowId; 36 | } 37 | 38 | public void setReplyFlowId(int flowId) { 39 | this.replyFlowId = flowId; 40 | } 41 | 42 | public int getReplyId() { 43 | return replyId; 44 | } 45 | 46 | public void setReplyId(int msgId) { 47 | this.replyId = msgId; 48 | } 49 | 50 | public byte getReplyCode() { 51 | return replyCode; 52 | } 53 | 54 | public void setReplyCode(byte code) { 55 | this.replyCode = code; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "ServerCommonRespMsg [replyFlowId=" + replyFlowId + ", replyId=" + replyId + ", replyCode=" + replyCode 61 | + "]"; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/service/BaseMsgProcessService.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.service; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import cn.hylexus.jt808.server.SessionManager; 7 | import cn.hylexus.jt808.vo.Session; 8 | import io.netty.buffer.ByteBuf; 9 | import io.netty.buffer.PooledByteBufAllocator; 10 | import io.netty.buffer.Unpooled; 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelFuture; 13 | 14 | public class BaseMsgProcessService { 15 | 16 | protected final Logger log = LoggerFactory.getLogger(getClass()); 17 | 18 | protected SessionManager sessionManager; 19 | 20 | public BaseMsgProcessService() { 21 | this.sessionManager = SessionManager.getInstance(); 22 | } 23 | 24 | protected ByteBuf getByteBuf(byte[] arr) { 25 | ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.directBuffer(arr.length); 26 | byteBuf.writeBytes(arr); 27 | return byteBuf; 28 | } 29 | 30 | public void send2Client(Channel channel, byte[] arr) throws InterruptedException { 31 | ChannelFuture future = channel.writeAndFlush(Unpooled.copiedBuffer(arr)).sync(); 32 | if (!future.isSuccess()) { 33 | log.error("发送数据出错:{}", future.cause()); 34 | } 35 | } 36 | 37 | protected int getFlowId(Channel channel, int defaultValue) { 38 | Session session = this.sessionManager.findBySessionId(Session.buildId(channel)); 39 | if (session == null) { 40 | return defaultValue; 41 | } 42 | 43 | return session.currentFlowId(); 44 | } 45 | 46 | protected int getFlowId(Channel channel) { 47 | return this.getFlowId(channel, 0); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/util/HexStringUtils.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.util; 2 | 3 | public class HexStringUtils { 4 | 5 | private static final char[] DIGITS_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 6 | 7 | protected static char[] encodeHex(byte[] data) { 8 | int l = data.length; 9 | char[] out = new char[l << 1]; 10 | for (int i = 0, j = 0; i < l; i++) { 11 | out[j++] = DIGITS_HEX[(0xF0 & data[i]) >>> 4]; 12 | out[j++] = DIGITS_HEX[0x0F & data[i]]; 13 | } 14 | return out; 15 | } 16 | 17 | protected static byte[] decodeHex(char[] data) { 18 | int len = data.length; 19 | if ((len & 0x01) != 0) { 20 | throw new RuntimeException("字符个数应该为偶数"); 21 | } 22 | byte[] out = new byte[len >> 1]; 23 | for (int i = 0, j = 0; j < len; i++) { 24 | int f = toDigit(data[j], j) << 4; 25 | j++; 26 | f |= toDigit(data[j], j); 27 | j++; 28 | out[i] = (byte) (f & 0xFF); 29 | } 30 | return out; 31 | } 32 | 33 | protected static int toDigit(char ch, int index) { 34 | int digit = Character.digit(ch, 16); 35 | if (digit == -1) { 36 | throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index); 37 | } 38 | return digit; 39 | } 40 | 41 | public static String toHexString(byte[] bs) { 42 | return new String(encodeHex(bs)); 43 | } 44 | 45 | public static String hexString2Bytes(String hex) { 46 | return new String(decodeHex(hex.toCharArray())); 47 | } 48 | 49 | public static byte[] chars2Bytes(char[] bs) { 50 | return decodeHex(bs); 51 | } 52 | 53 | public static void main(String[] args) { 54 | String s = "abc你好"; 55 | String hex = toHexString(s.getBytes()); 56 | String decode = hexString2Bytes(hex); 57 | System.out.println("原字符串:" + s); 58 | System.out.println("十六进制字符串:" + hex); 59 | System.out.println("还原:" + decode); 60 | } 61 | } -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/util/BCD8421Operater.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.util; 2 | 3 | public class BCD8421Operater { 4 | 5 | 6 | 7 | /** 8 | * 9 | * @param bytes 10 | * @return 将二进制转换为十六进制字符输出 11 | */ 12 | public static String parseByte2HexStr(byte buf[]) { 13 | StringBuffer sb = new StringBuffer(); 14 | for (int i = 0; i < buf.length; i++) { 15 | String hex = Integer.toHexString(buf[i] & 0xFF); 16 | if (hex.length() == 1) { 17 | hex = '0' + hex; 18 | } 19 | sb.append(hex.toUpperCase()); 20 | } 21 | return sb.toString(); 22 | } 23 | 24 | /** 25 | * BCD字节数组===>String 26 | * 27 | * @param bytes 28 | * @return 十进制字符串 29 | */ 30 | public String bcd2String(byte[] bytes) { 31 | StringBuilder temp = new StringBuilder(bytes.length * 2); 32 | for (int i = 0; i < bytes.length; i++) { 33 | // 高四位 34 | temp.append((bytes[i] & 0xf0) >>> 4); 35 | // 低四位 36 | temp.append(bytes[i] & 0x0f); 37 | } 38 | return temp.toString().substring(0, 1).equalsIgnoreCase("0") ? temp.toString().substring(1) : temp.toString(); 39 | } 40 | 41 | /** 42 | * 字符串==>BCD字节数组 43 | * 44 | * @param str 45 | * @return BCD字节数组 46 | */ 47 | public byte[] string2Bcd(String str) { 48 | // 奇数,前补零 49 | if ((str.length() & 0x1) == 1) { 50 | str = "0" + str; 51 | } 52 | 53 | byte ret[] = new byte[str.length() / 2]; 54 | byte bs[] = str.getBytes(); 55 | for (int i = 0; i < ret.length; i++) { 56 | 57 | byte high = ascII2Bcd(bs[2 * i]); 58 | byte low = ascII2Bcd(bs[2 * i + 1]); 59 | 60 | // TODO 只遮罩BCD低四位? 61 | ret[i] = (byte) ((high << 4) | low); 62 | } 63 | return ret; 64 | } 65 | 66 | private byte ascII2Bcd(byte asc) { 67 | if ((asc >= '0') && (asc <= '9')) 68 | return (byte) (asc - '0'); 69 | else if ((asc >= 'A') && (asc <= 'F')) 70 | return (byte) (asc - 'A' + 10); 71 | else if ((asc >= 'a') && (asc <= 'f')) 72 | return (byte) (asc - 'a' + 10); 73 | else 74 | return (byte) (asc - 48); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/client/Client.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.EventLoopGroup; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import io.netty.handler.codec.DelimiterBasedFrameDecoder; 13 | import io.netty.handler.codec.string.StringDecoder; 14 | import io.netty.handler.codec.string.StringEncoder; 15 | 16 | public class Client { 17 | public static void main(String[] args) throws Exception { 18 | //1、创建线程组,客户端和服务端不一样,客户端只需要一个即可 19 | EventLoopGroup workGroup = new NioEventLoopGroup(); 20 | //创建辅助类,和服务端的不一样,服务端的是ServerBootstrap,而客户端只是BootStrap 21 | Bootstrap bs = new Bootstrap(); 22 | //加入线程组 23 | bs.group(workGroup) 24 | //指定通道类型 25 | .channel(NioSocketChannel.class) 26 | //绑定事件处理器 27 | .handler(new ChannelInitializer() { 28 | @Override 29 | protected void initChannel(SocketChannel sc) throws Exception { 30 | //设置特殊分隔符 31 | ByteBuf buf = Unpooled.copiedBuffer(new byte[] { 0x7e }); 32 | ByteBuf buf2 = Unpooled.copiedBuffer(new byte[] { 0x7e}); 33 | 34 | sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf,buf2)); 35 | //设置字符串形式的编码 36 | sc.pipeline().addLast(new StringEncoder()); 37 | //设置字符串解码 38 | sc.pipeline().addLast(new StringDecoder()); 39 | sc.pipeline().addLast(new ClientHandler()); 40 | } 41 | }); 42 | 43 | //链接服务端 44 | ChannelFuture cf = bs.connect("127.0.0.1", 20048).sync(); 45 | //给服务端写数据,现在不再需要是写缓冲区了,因为我们上面的配置加了字符串的编码,直接写入字符串即可。 46 | cf.channel().writeAndFlush("0x7E0200003801328226177202C300000000200C00C30245427806BCC868007A02E4001F18112710100301040007D300030202DA2504000000002B040021000030011931010F1F0x7E"); //记得加上$_,因为这个分隔符是拿来判断消息是否该发送的。 47 | 48 | //异步监听管道的关闭,如果关闭了就往下继续执行 49 | cf.channel().closeFuture().sync(); 50 | workGroup.shutdownGracefully(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jt808-tcp-netty/.gitignore: -------------------------------------------------------------------------------- 1 | ### Eclipse template 2 | *.pydevproject 3 | .metadata 4 | .gradle 5 | bin/ 6 | tmp/ 7 | *.tmp 8 | *.bak 9 | *.swp 10 | *~.nib 11 | local.properties 12 | .settings/ 13 | .loadpath 14 | 15 | # Eclipse Core 16 | .project 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # CDT-specific 25 | .cproject 26 | 27 | # JDT-specific (Eclipse Java Development Tools) 28 | .classpath 29 | 30 | # Java annotation processor (APT) 31 | .factorypath 32 | 33 | # PDT-specific 34 | .buildpath 35 | 36 | # sbteclipse plugin 37 | .target 38 | 39 | # TeXlipse plugin 40 | .texlipse 41 | 42 | 43 | ### JetBrains template 44 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 45 | 46 | *.iml 47 | 48 | ## Directory-based project format: 49 | .idea/ 50 | # if you remove the above rule, at least ignore the following: 51 | 52 | # User-specific stuff: 53 | # .idea/workspace.xml 54 | # .idea/tasks.xml 55 | # .idea/dictionaries 56 | 57 | # Sensitive or high-churn files: 58 | # .idea/dataSources.ids 59 | # .idea/dataSources.xml 60 | # .idea/sqlDataSources.xml 61 | # .idea/dynamic.xml 62 | # .idea/uiDesigner.xml 63 | 64 | # Gradle: 65 | # .idea/gradle.xml 66 | # .idea/libraries 67 | 68 | # Mongo Explorer plugin: 69 | # .idea/mongoSettings.xml 70 | 71 | ## File-based project format: 72 | *.ipr 73 | *.iws 74 | 75 | ## Plugin-specific files: 76 | 77 | # IntelliJ 78 | /out/ 79 | 80 | # mpeltonen/sbt-idea plugin 81 | .idea_modules/ 82 | 83 | # JIRA plugin 84 | atlassian-ide-plugin.xml 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | 91 | 92 | ### Java template 93 | *.class 94 | 95 | # Mobile Tools for Java (J2ME) 96 | .mtj.tmp/ 97 | 98 | # Package Files # 99 | *.jar 100 | *.war 101 | *.ear 102 | 103 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 104 | hs_err_pid* 105 | 106 | 107 | /project/project/ 108 | /project/target/ 109 | /project/activator-* 110 | /logs/ 111 | /RUNNING_PID 112 | .DS_Store 113 | /target/ 114 | /workspace/ 115 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/vo/req/LocationInfoUploadMsg.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.vo.req; 2 | 3 | 4 | import cn.hylexus.jt808.vo.PackageData; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * 位置信息汇报消息 10 | * 11 | * @author hylexus 12 | * 13 | */ 14 | public class LocationInfoUploadMsg extends PackageData { 15 | // 告警信息 16 | // byte[0-3] 17 | private int warningFlagField; 18 | // byte[4-7] 状态(DWORD(32)) 19 | private int statusField; 20 | // byte[8-11] 纬度(DWORD(32)) 21 | private float latitude; 22 | // byte[12-15] 经度(DWORD(32)) 23 | private float longitude; 24 | // byte[16-17] 高程(WORD(16)) 海拔高度,单位为米( m) 25 | // TODO ==>int?海拔 26 | private int elevation; 27 | // byte[18-19] 速度(WORD) 1/10km/h 28 | // TODO ==>float?速度 29 | private float speed; 30 | // byte[20-21] 方向(WORD) 0-359,正北为 0,顺时针 31 | private int direction; 32 | // byte[22-x] 时间(BCD[6]) YY-MM-DD-hh-mm-ss 33 | // GMT+8 时间,本标准中之后涉及的时间均采用此时区 34 | private Date time; 35 | 36 | public LocationInfoUploadMsg() { 37 | } 38 | 39 | public LocationInfoUploadMsg(PackageData packageData) { 40 | this(); 41 | this.channel = packageData.getChannel(); 42 | this.checkSum = packageData.getCheckSum(); 43 | this.msgBodyBytes = packageData.getMsgBodyBytes(); 44 | this.msgHeader = packageData.getMsgHeader(); 45 | } 46 | 47 | public float getLatitude() { 48 | return latitude; 49 | } 50 | 51 | public void setLatitude(float latitude) { 52 | this.latitude = latitude; 53 | } 54 | 55 | public float getLongitude() { 56 | return longitude; 57 | } 58 | 59 | public void setLongitude(float longitude) { 60 | this.longitude = longitude; 61 | } 62 | 63 | public int getElevation() { 64 | return elevation; 65 | } 66 | 67 | public void setElevation(int elevation) { 68 | this.elevation = elevation; 69 | } 70 | 71 | public float getSpeed() { 72 | return speed; 73 | } 74 | 75 | public void setSpeed(float speed) { 76 | this.speed = speed; 77 | } 78 | 79 | public int getDirection() { 80 | return direction; 81 | } 82 | 83 | public void setDirection(int direction) { 84 | this.direction = direction; 85 | } 86 | 87 | public Date getTime() { 88 | return time; 89 | } 90 | 91 | public void setTime(Date time) { 92 | this.time = time; 93 | } 94 | 95 | public int getWarningFlagField() { 96 | return warningFlagField; 97 | } 98 | 99 | public void setWarningFlagField(int warningFlagField) { 100 | this.warningFlagField = warningFlagField; 101 | } 102 | 103 | public int getStatusField() { 104 | return statusField; 105 | } 106 | 107 | public void setStatusField(int statusField) { 108 | this.statusField = statusField; 109 | } 110 | 111 | @Override 112 | public String toString() { 113 | return "LocationInfoUploadMsg [warningFlagField=" + warningFlagField + ", statusField=" + statusField 114 | + ", latitude=" + latitude + ", longitude=" + longitude + ", elevation=" + elevation + ", speed=" 115 | + speed + ", direction=" + direction + ", time=" + time + "]"; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /jt808-tcp-netty/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | cn.hylexus 6 | jt808-tcp-netty 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | jt808-tcp-netty 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | yyyyMMdd 16 | 4.2.0.RELEASE 17 | 18 | 19 | 20 | 21 | 22 | alimaven 23 | aliyun maven 24 | http://maven.aliyun.com/nexus/content/groups/public/ 25 | 26 | true 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | alimaven 36 | aliyun maven 37 | http://maven.aliyun.com/nexus/content/groups/public/ 38 | 39 | true 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | junit 51 | junit 52 | 4.11 53 | test 54 | 55 | 56 | 57 | 58 | 59 | com.alibaba 60 | fastjson 61 | 1.2.8 62 | 63 | 64 | 65 | 66 | log4j 67 | log4j 68 | 1.2.17 69 | 70 | 71 | org.slf4j 72 | slf4j-api 73 | 1.7.5 74 | 75 | 76 | org.slf4j 77 | slf4j-log4j12 78 | 1.7.5 79 | 80 | 81 | 82 | 83 | 84 | io.netty 85 | netty-all 86 | 4.1.6.Final 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-compiler-plugin 98 | 3.5.1 99 | 100 | 1.8 101 | 1.8 102 | UTF-8 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/vo/Session.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.vo; 2 | 3 | import java.net.SocketAddress; 4 | 5 | import io.netty.channel.Channel; 6 | 7 | public class Session { 8 | 9 | private String id; 10 | private String terminalPhone; 11 | private Channel channel = null; 12 | private boolean isAuthenticated = false; 13 | // 消息流水号 word(16) 按发送顺序从 0 开始循环累加 14 | private int currentFlowId = 0; 15 | // private ChannelGroup channelGroup = new 16 | // DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 17 | // 客户端上次的连接时间,该值改变的情况: 18 | // 1. terminal --> server 心跳包 19 | // 2. terminal --> server 数据包 20 | private long lastCommunicateTimeStamp = 0l; 21 | 22 | public Session() { 23 | } 24 | 25 | public Channel getChannel() { 26 | return channel; 27 | } 28 | 29 | public void setChannel(Channel channel) { 30 | this.channel = channel; 31 | } 32 | 33 | public String getTerminalPhone() { 34 | return terminalPhone; 35 | } 36 | 37 | public void setTerminalPhone(String terminalPhone) { 38 | this.terminalPhone = terminalPhone; 39 | } 40 | 41 | public void setId(String id) { 42 | this.id = id; 43 | } 44 | 45 | public String getId() { 46 | return id; 47 | } 48 | 49 | public static String buildId(Channel channel) { 50 | return channel.id().asLongText(); 51 | } 52 | 53 | public static Session buildSession(Channel channel) { 54 | return buildSession(channel, null); 55 | } 56 | 57 | public static Session buildSession(Channel channel, String phone) { 58 | Session session = new Session(); 59 | session.setChannel(channel); 60 | session.setId(buildId(channel)); 61 | session.setTerminalPhone(phone); 62 | session.setLastCommunicateTimeStamp(System.currentTimeMillis()); 63 | return session; 64 | } 65 | 66 | public long getLastCommunicateTimeStamp() { 67 | return lastCommunicateTimeStamp; 68 | } 69 | 70 | public void setLastCommunicateTimeStamp(long lastCommunicateTimeStamp) { 71 | this.lastCommunicateTimeStamp = lastCommunicateTimeStamp; 72 | } 73 | 74 | public SocketAddress getRemoteAddr() { 75 | System.out.println(this.channel.remoteAddress().getClass()); 76 | 77 | return this.channel.remoteAddress(); 78 | } 79 | 80 | public boolean isAuthenticated() { 81 | return isAuthenticated; 82 | } 83 | 84 | public void setAuthenticated(boolean isAuthenticated) { 85 | this.isAuthenticated = isAuthenticated; 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | final int prime = 31; 91 | int result = 1; 92 | result = prime * result + ((id == null) ? 0 : id.hashCode()); 93 | return result; 94 | } 95 | 96 | @Override 97 | public boolean equals(Object obj) { 98 | if (this == obj) 99 | return true; 100 | if (obj == null) 101 | return false; 102 | if (getClass() != obj.getClass()) 103 | return false; 104 | Session other = (Session) obj; 105 | if (id == null) { 106 | if (other.id != null) 107 | return false; 108 | } else if (!id.equals(other.id)) 109 | return false; 110 | return true; 111 | } 112 | 113 | @Override 114 | public String toString() { 115 | return "Session [id=" + id + ", terminalPhone=" + terminalPhone + ", channel=" + channel + "]"; 116 | } 117 | 118 | public synchronized int currentFlowId() { 119 | if (currentFlowId >= 0xffff) 120 | currentFlowId = 0; 121 | return currentFlowId++; 122 | } 123 | 124 | } -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/server/SessionManager.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.server; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | import java.util.Set; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.function.BiConsumer; 9 | import java.util.stream.Collectors; 10 | 11 | import cn.hylexus.jt808.vo.Session; 12 | 13 | public class SessionManager { 14 | 15 | private static volatile SessionManager instance = null; 16 | // netty生成的sessionID和Session的对应关系 17 | private Map sessionIdMap; 18 | // 终端手机号和netty生成的sessionID的对应关系 19 | private Map phoneMap; 20 | 21 | public static SessionManager getInstance() { 22 | if (instance == null) { 23 | synchronized (SessionManager.class) { 24 | if (instance == null) { 25 | instance = new SessionManager(); 26 | } 27 | } 28 | } 29 | return instance; 30 | } 31 | 32 | public SessionManager() { 33 | this.sessionIdMap = new ConcurrentHashMap<>(); 34 | this.phoneMap = new ConcurrentHashMap<>(); 35 | } 36 | 37 | public boolean containsKey(String sessionId) { 38 | return sessionIdMap.containsKey(sessionId); 39 | } 40 | 41 | public boolean containsSession(Session session) { 42 | return sessionIdMap.containsValue(session); 43 | } 44 | 45 | public Session findBySessionId(String id) { 46 | return sessionIdMap.get(id); 47 | } 48 | 49 | public Session findByTerminalPhone(String phone) { 50 | String sessionId = this.phoneMap.get(phone); 51 | if (sessionId == null) 52 | return null; 53 | return this.findBySessionId(sessionId); 54 | } 55 | 56 | public synchronized Session put(String key, Session value) { 57 | if (value.getTerminalPhone() != null && !"".equals(value.getTerminalPhone().trim())) { 58 | this.phoneMap.put(value.getTerminalPhone(), value.getId()); 59 | } 60 | return sessionIdMap.put(key, value); 61 | } 62 | 63 | public synchronized Session removeBySessionId(String sessionId) { 64 | if (sessionId == null) 65 | return null; 66 | Session session = sessionIdMap.remove(sessionId); 67 | if (session == null) 68 | return null; 69 | if (session.getTerminalPhone() != null) 70 | this.phoneMap.remove(session.getTerminalPhone()); 71 | return session; 72 | } 73 | 74 | // public synchronized void remove(String sessionId) { 75 | // if (sessionId == null) 76 | // return; 77 | // Session session = sessionIdMap.remove(sessionId); 78 | // if (session == null) 79 | // return; 80 | // if (session.getTerminalPhone() != null) 81 | // this.phoneMap.remove(session.getTerminalPhone()); 82 | // try { 83 | // if (session.getChannel() != null) { 84 | // if (session.getChannel().isActive() || session.getChannel().isOpen()) { 85 | // session.getChannel().close(); 86 | // } 87 | // session = null; 88 | // } 89 | // } catch (Exception e) { 90 | // e.printStackTrace(); 91 | // } 92 | // } 93 | 94 | public Set keySet() { 95 | return sessionIdMap.keySet(); 96 | } 97 | 98 | public void forEach(BiConsumer action) { 99 | sessionIdMap.forEach(action); 100 | } 101 | 102 | public Set> entrySet() { 103 | return sessionIdMap.entrySet(); 104 | } 105 | 106 | public List toList() { 107 | return this.sessionIdMap.entrySet().stream().map(e -> e.getValue()).collect(Collectors.toList()); 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/vo/req/TerminalRegisterMsg.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.vo.req; 2 | 3 | import java.util.Arrays; 4 | 5 | import cn.hylexus.jt808.vo.PackageData; 6 | 7 | /** 8 | * 终端注册消息 9 | * 10 | * @author hylexus 11 | * 12 | */ 13 | public class TerminalRegisterMsg extends PackageData { 14 | 15 | private TerminalRegInfo terminalRegInfo; 16 | 17 | public TerminalRegisterMsg() { 18 | } 19 | 20 | public TerminalRegisterMsg(PackageData packageData) { 21 | this(); 22 | this.channel = packageData.getChannel(); 23 | this.checkSum = packageData.getCheckSum(); 24 | this.msgBodyBytes = packageData.getMsgBodyBytes(); 25 | this.msgHeader = packageData.getMsgHeader(); 26 | } 27 | 28 | public TerminalRegInfo getTerminalRegInfo() { 29 | return terminalRegInfo; 30 | } 31 | 32 | public void setTerminalRegInfo(TerminalRegInfo msgBody) { 33 | this.terminalRegInfo = msgBody; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return "TerminalRegisterMsg [terminalRegInfo=" + terminalRegInfo + ", msgHeader=" + msgHeader 39 | + ", msgBodyBytes=" + Arrays.toString(msgBodyBytes) + ", checkSum=" + checkSum + ", channel=" + channel 40 | + "]"; 41 | } 42 | 43 | public static class TerminalRegInfo { 44 | // 省域ID(WORD),设备安装车辆所在的省域,省域ID采用GB/T2260中规定的行政区划代码6位中前两位 45 | // 0保留,由平台取默认值 46 | private int provinceId; 47 | // 市县域ID(WORD) 设备安装车辆所在的市域或县域,市县域ID采用GB/T2260中规定的行 政区划代码6位中后四位 48 | // 0保留,由平台取默认值 49 | private int cityId; 50 | // 制造商ID(BYTE[5]) 5 个字节,终端制造商编码 51 | private String manufacturerId; 52 | // 终端型号(BYTE[8]) 八个字节, 此终端型号 由制造商自行定义 位数不足八位的,补空格。 53 | private String terminalType; 54 | // 终端ID(BYTE[7]) 七个字节, 由大写字母 和数字组成, 此终端 ID由制造 商自行定义 55 | private String terminalId; 56 | /** 57 | * 58 | * 车牌颜色(BYTE) 车牌颜色,按照 JT/T415-2006 的 5.4.12 未上牌时,取值为0
59 | * 0===未上车牌
60 | * 1===蓝色
61 | * 2===黄色
62 | * 3===黑色
63 | * 4===白色
64 | * 9===其他 65 | */ 66 | private int licensePlateColor; 67 | // 车牌(STRING) 公安交 通管理部门颁 发的机动车号牌 68 | private String licensePlate; 69 | 70 | public TerminalRegInfo() { 71 | } 72 | 73 | public int getProvinceId() { 74 | return provinceId; 75 | } 76 | 77 | public void setProvinceId(int provinceId) { 78 | this.provinceId = provinceId; 79 | } 80 | 81 | public int getCityId() { 82 | return cityId; 83 | } 84 | 85 | public void setCityId(int cityId) { 86 | this.cityId = cityId; 87 | } 88 | 89 | public String getManufacturerId() { 90 | return manufacturerId; 91 | } 92 | 93 | public void setManufacturerId(String manufacturerId) { 94 | this.manufacturerId = manufacturerId; 95 | } 96 | 97 | public String getTerminalType() { 98 | return terminalType; 99 | } 100 | 101 | public void setTerminalType(String terminalType) { 102 | this.terminalType = terminalType; 103 | } 104 | 105 | public String getTerminalId() { 106 | return terminalId; 107 | } 108 | 109 | public void setTerminalId(String terminalId) { 110 | this.terminalId = terminalId; 111 | } 112 | 113 | public int getLicensePlateColor() { 114 | return licensePlateColor; 115 | } 116 | 117 | public void setLicensePlateColor(int licensePlate) { 118 | this.licensePlateColor = licensePlate; 119 | } 120 | 121 | public String getLicensePlate() { 122 | return licensePlate; 123 | } 124 | 125 | public void setLicensePlate(String licensePlate) { 126 | this.licensePlate = licensePlate; 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | return "TerminalRegInfo [provinceId=" + provinceId + ", cityId=" + cityId + ", manufacturerId=" 132 | + manufacturerId + ", terminalType=" + terminalType + ", terminalId=" + terminalId 133 | + ", licensePlateColor=" + licensePlateColor + ", licensePlate=" + licensePlate + "]"; 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/server/TCPServer.java: -------------------------------------------------------------------------------- 1 | 2 | package cn.hylexus.jt808.server; 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import cn.hylexus.jt808.common.TPMSConsts; 9 | import cn.hylexus.jt808.service.codec.Decoder4LoggingOnly; 10 | import cn.hylexus.jt808.service.handler.TCPServerHandler; 11 | import io.netty.bootstrap.ServerBootstrap; 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.buffer.Unpooled; 14 | import io.netty.channel.ChannelFuture; 15 | import io.netty.channel.ChannelInitializer; 16 | import io.netty.channel.ChannelOption; 17 | import io.netty.channel.EventLoopGroup; 18 | import io.netty.channel.nio.NioEventLoopGroup; 19 | import io.netty.channel.socket.SocketChannel; 20 | import io.netty.channel.socket.nio.NioServerSocketChannel; 21 | import io.netty.handler.codec.DelimiterBasedFrameDecoder; 22 | import io.netty.handler.timeout.IdleStateHandler; 23 | import io.netty.util.concurrent.Future; 24 | 25 | public class TCPServer { 26 | 27 | private Logger log = LoggerFactory.getLogger(getClass()); 28 | private volatile boolean isRunning = false; 29 | 30 | private EventLoopGroup bossGroup = null; 31 | private EventLoopGroup workerGroup = null; 32 | private int port; 33 | 34 | public TCPServer() { 35 | } 36 | 37 | public TCPServer(int port) { 38 | this(); 39 | this.port = port; 40 | } 41 | 42 | private void bind() throws Exception { 43 | this.bossGroup = new NioEventLoopGroup(); 44 | this.workerGroup = new NioEventLoopGroup(); 45 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 46 | serverBootstrap.group(bossGroup, workerGroup)// 47 | .channel(NioServerSocketChannel.class) // 48 | .childHandler(new ChannelInitializer() { // 49 | @Override 50 | public void initChannel(SocketChannel ch) throws Exception { 51 | ch.pipeline().addLast("idleStateHandler", 52 | new IdleStateHandler(TPMSConsts.tcp_client_idle_minutes, 0, 0, TimeUnit.MINUTES)); 53 | ch.pipeline().addLast(new Decoder4LoggingOnly()); 54 | // 1024表示单条消息的最大长度,解码器在查找分隔符的时候,达到该长度还没找到的话会抛异常 55 | // ch.pipeline().addLast( 56 | // new DelimiterBasedFrameDecoder(2048, Unpooled.copiedBuffer(new byte[] { 0x7e }), 57 | // Unpooled.copiedBuffer(new byte[] { 0x7e, 0x7e }))); 58 | 59 | // 分隔符解码方式 60 | ByteBuf delimiter = Unpooled.copiedBuffer(new byte[]{0x7e}); 61 | ch.pipeline().addLast( 62 | new DelimiterBasedFrameDecoder(1024, delimiter,delimiter)); 63 | 64 | //ch.pipeline().addLast(new PackageDataDecoder()); 65 | ch.pipeline().addLast(new TCPServerHandler()); 66 | } 67 | }).option(ChannelOption.SO_BACKLOG, 128) // 68 | .childOption(ChannelOption.SO_KEEPALIVE, true); 69 | 70 | this.log.info("TCP服务启动完毕,port={}", this.port); 71 | ChannelFuture channelFuture = serverBootstrap.bind(port).sync(); 72 | 73 | channelFuture.channel().closeFuture().sync(); 74 | } 75 | 76 | public synchronized void startServer() { 77 | if (this.isRunning) { 78 | throw new IllegalStateException(this.getName() + " is already started ."); 79 | } 80 | this.isRunning = true; 81 | 82 | new Thread(() -> { 83 | try { 84 | this.bind(); 85 | } catch (Exception e) { 86 | this.log.info("TCP服务启动出错:{}", e.getMessage()); 87 | e.printStackTrace(); 88 | } 89 | }, this.getName()).start(); 90 | } 91 | 92 | public synchronized void stopServer() { 93 | if (!this.isRunning) { 94 | throw new IllegalStateException(this.getName() + " is not yet started ."); 95 | } 96 | this.isRunning = false; 97 | 98 | try { 99 | Future future = this.workerGroup.shutdownGracefully().await(); 100 | if (!future.isSuccess()) { 101 | log.error("workerGroup 无法正常停止:{}", future.cause()); 102 | } 103 | 104 | future = this.bossGroup.shutdownGracefully().await(); 105 | if (!future.isSuccess()) { 106 | log.error("bossGroup 无法正常停止:{}", future.cause()); 107 | } 108 | } catch (InterruptedException e) { 109 | e.printStackTrace(); 110 | } 111 | 112 | this.log.info("TCP服务已经停止..."); 113 | } 114 | 115 | private String getName() { 116 | return "TCP-Server"; 117 | } 118 | 119 | public static void main(String[] args) throws Exception { 120 | TCPServer server = new TCPServer(20048); 121 | server.startServer(); 122 | 123 | // Thread.sleep(3000); 124 | // server.stopServer(); 125 | } 126 | } -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/service/codec/MsgEncoder.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.service.codec; 2 | 3 | import java.util.Arrays; 4 | 5 | import cn.hylexus.jt808.common.TPMSConsts; 6 | import cn.hylexus.jt808.util.BitOperator; 7 | import cn.hylexus.jt808.util.JT808ProtocolUtils; 8 | import cn.hylexus.jt808.vo.PackageData; 9 | import cn.hylexus.jt808.vo.Session; 10 | import cn.hylexus.jt808.vo.req.TerminalRegisterMsg; 11 | import cn.hylexus.jt808.vo.resp.ServerCommonRespMsgBody; 12 | import cn.hylexus.jt808.vo.resp.TerminalRegisterMsgRespBody; 13 | 14 | public class MsgEncoder { 15 | private BitOperator bitOperator; 16 | private JT808ProtocolUtils jt808ProtocolUtils; 17 | 18 | public MsgEncoder() { 19 | this.bitOperator = new BitOperator(); 20 | this.jt808ProtocolUtils = new JT808ProtocolUtils(); 21 | } 22 | 23 | public byte[] encode4TerminalRegisterResp(TerminalRegisterMsg req, TerminalRegisterMsgRespBody respMsgBody, 24 | int flowId) throws Exception { 25 | // 消息体字节数组 26 | byte[] msgBody = null; 27 | // 鉴权码(STRING) 只有在成功后才有该字段 28 | if (respMsgBody.getReplyCode() == TerminalRegisterMsgRespBody.success) { 29 | msgBody = this.bitOperator.concatAll(Arrays.asList(// 30 | bitOperator.integerTo2Bytes(respMsgBody.getReplyFlowId()), // 流水号(2) 31 | new byte[] { respMsgBody.getReplyCode() }, // 结果 32 | respMsgBody.getReplyToken().getBytes(TPMSConsts.string_charset)// 鉴权码(STRING) 33 | )); 34 | } else { 35 | msgBody = this.bitOperator.concatAll(Arrays.asList(// 36 | bitOperator.integerTo2Bytes(respMsgBody.getReplyFlowId()), // 流水号(2) 37 | new byte[] { respMsgBody.getReplyCode() }// 错误代码 38 | )); 39 | } 40 | 41 | // 消息头 42 | int msgBodyProps = this.jt808ProtocolUtils.generateMsgBodyProps(msgBody.length, 0b000, false, 0); 43 | byte[] msgHeader = this.jt808ProtocolUtils.generateMsgHeader(req.getMsgHeader().getTerminalPhone(), 44 | TPMSConsts.cmd_terminal_register_resp, msgBody, msgBodyProps, flowId); 45 | byte[] headerAndBody = this.bitOperator.concatAll(msgHeader, msgBody); 46 | 47 | // 校验码 48 | //int checkSum = this.bitOperator.getCheckSum4JT808(headerAndBody, 0, headerAndBody.length - 1); 49 | //作用是把校验码去掉。编码器这里就不应该 -1 了。 50 | int checkSum = this.bitOperator.getCheckSum4JT808(headerAndBody, 0, headerAndBody.length); 51 | 52 | // 连接并且转义 53 | return this.doEncode(headerAndBody, checkSum); 54 | } 55 | 56 | // public byte[] encode4ServerCommonRespMsg(TerminalAuthenticationMsg req, 57 | // ServerCommonRespMsgBody respMsgBody, int flowId) throws Exception { 58 | public byte[] encode4ServerCommonRespMsg(PackageData req, ServerCommonRespMsgBody respMsgBody, int flowId) 59 | throws Exception { 60 | byte[] msgBody = this.bitOperator.concatAll(Arrays.asList(// 61 | bitOperator.integerTo2Bytes(respMsgBody.getReplyFlowId()), // 应答流水号 62 | bitOperator.integerTo2Bytes(respMsgBody.getReplyId()), // 应答ID,对应的终端消息的ID 63 | new byte[] { respMsgBody.getReplyCode() }// 结果 64 | )); 65 | 66 | // 消息头 67 | int msgBodyProps = this.jt808ProtocolUtils.generateMsgBodyProps(msgBody.length, 0b000, false, 0); 68 | byte[] msgHeader = this.jt808ProtocolUtils.generateMsgHeader(req.getMsgHeader().getTerminalPhone(), 69 | TPMSConsts.cmd_common_resp, msgBody, msgBodyProps, flowId); 70 | byte[] headerAndBody = this.bitOperator.concatAll(msgHeader, msgBody); 71 | // 校验码 72 | int checkSum = this.bitOperator.getCheckSum4JT808(headerAndBody, 0, headerAndBody.length - 1); 73 | // 连接并且转义 74 | return this.doEncode(headerAndBody, checkSum); 75 | } 76 | 77 | public byte[] encode4ParamSetting(byte[] msgBodyBytes, Session session) throws Exception { 78 | // 消息头 79 | int msgBodyProps = this.jt808ProtocolUtils.generateMsgBodyProps(msgBodyBytes.length, 0b000, false, 0); 80 | byte[] msgHeader = this.jt808ProtocolUtils.generateMsgHeader(session.getTerminalPhone(), 81 | TPMSConsts.cmd_terminal_param_settings, msgBodyBytes, msgBodyProps, session.currentFlowId()); 82 | // 连接消息头和消息体 83 | byte[] headerAndBody = this.bitOperator.concatAll(msgHeader, msgBodyBytes); 84 | // 校验码 85 | int checkSum = this.bitOperator.getCheckSum4JT808(headerAndBody, 0, headerAndBody.length - 1); 86 | // 连接并且转义 87 | return this.doEncode(headerAndBody, checkSum); 88 | } 89 | 90 | private byte[] doEncode(byte[] headerAndBody, int checkSum) throws Exception { 91 | byte[] noEscapedBytes = this.bitOperator.concatAll(Arrays.asList(// 92 | new byte[] { TPMSConsts.pkg_delimiter }, // 0x7e 93 | headerAndBody, // 消息头+ 消息体 94 | bitOperator.integerTo1Bytes(checkSum), // 校验码 95 | new byte[] { TPMSConsts.pkg_delimiter }// 0x7e 96 | )); 97 | // 转义 98 | return jt808ProtocolUtils.doEscape4Send(noEscapedBytes, 1, noEscapedBytes.length - 2); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/util/JT808ProtocolUtils.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.util; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * JT808协议转义工具类 10 | * 11 | *
 12 |  * 0x7d01 <====> 0x7d
 13 |  * 0x7d02 <====> 0x7e
 14 |  * 
15 | * 16 | * @author hylexus 17 | * 18 | */ 19 | public class JT808ProtocolUtils { 20 | private final Logger log = LoggerFactory.getLogger(getClass()); 21 | private BitOperator bitOperator; 22 | private BCD8421Operater bcd8421Operater; 23 | 24 | public JT808ProtocolUtils() { 25 | this.bitOperator = new BitOperator(); 26 | this.bcd8421Operater = new BCD8421Operater(); 27 | } 28 | 29 | /** 30 | * 接收消息时转义
31 | * 32 | *
 33 | 	 * 0x7d01 <====> 0x7d
 34 | 	 * 0x7d02 <====> 0x7e
 35 | 	 * 
36 | * 37 | * @param bs 38 | * 要转义的字节数组 39 | * @param start 40 | * 起始索引 41 | * @param end 42 | * 结束索引 43 | * @return 转义后的字节数组 44 | * @throws Exception 45 | */ 46 | public byte[] doEscape4Receive(byte[] bs, int start, int end) throws Exception { 47 | if (start < 0 || end > bs.length) 48 | throw new ArrayIndexOutOfBoundsException("doEscape4Receive error : index out of bounds(start=" + start 49 | + ",end=" + end + ",bytes length=" + bs.length + ")"); 50 | ByteArrayOutputStream baos = null; 51 | try { 52 | baos = new ByteArrayOutputStream(); 53 | for (int i = 0; i < start; i++) { 54 | baos.write(bs[i]); 55 | } 56 | for (int i = start; i < end - 1; i++) { 57 | if (bs[i] == 0x7d && bs[i + 1] == 0x01) { 58 | baos.write(0x7d); 59 | i++; 60 | } else if (bs[i] == 0x7d && bs[i + 1] == 0x02) { 61 | baos.write(0x7e); 62 | i++; 63 | } else { 64 | baos.write(bs[i]); 65 | } 66 | } 67 | for (int i = end - 1; i < bs.length; i++) { 68 | baos.write(bs[i]); 69 | } 70 | return baos.toByteArray(); 71 | } catch (Exception e) { 72 | throw e; 73 | } finally { 74 | if (baos != null) { 75 | baos.close(); 76 | baos = null; 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * 83 | * 发送消息时转义
84 | * 85 | *
 86 | 	 *  0x7e <====> 0x7d02
 87 | 	 * 
88 | * 89 | * @param bs 90 | * 要转义的字节数组 91 | * @param start 92 | * 起始索引 93 | * @param end 94 | * 结束索引 95 | * @return 转义后的字节数组 96 | * @throws Exception 97 | */ 98 | public byte[] doEscape4Send(byte[] bs, int start, int end) throws Exception { 99 | if (start < 0 || end > bs.length) 100 | throw new ArrayIndexOutOfBoundsException("doEscape4Send error : index out of bounds(start=" + start 101 | + ",end=" + end + ",bytes length=" + bs.length + ")"); 102 | ByteArrayOutputStream baos = null; 103 | try { 104 | baos = new ByteArrayOutputStream(); 105 | for (int i = 0; i < start; i++) { 106 | baos.write(bs[i]); 107 | } 108 | for (int i = start; i < end; i++) { 109 | if (bs[i] == 0x7e) { 110 | baos.write(0x7d); 111 | baos.write(0x02); 112 | } else { 113 | baos.write(bs[i]); 114 | } 115 | } 116 | for (int i = end; i < bs.length; i++) { 117 | baos.write(bs[i]); 118 | } 119 | return baos.toByteArray(); 120 | } catch (Exception e) { 121 | throw e; 122 | } finally { 123 | if (baos != null) { 124 | baos.close(); 125 | baos = null; 126 | } 127 | } 128 | } 129 | 130 | public int generateMsgBodyProps(int msgLen, int enctyptionType, boolean isSubPackage, int reversed_14_15) { 131 | // [ 0-9 ] 0000,0011,1111,1111(3FF)(消息体长度) 132 | // [10-12] 0001,1100,0000,0000(1C00)(加密类型) 133 | // [ 13_ ] 0010,0000,0000,0000(2000)(是否有子包) 134 | // [14-15] 1100,0000,0000,0000(C000)(保留位) 135 | if (msgLen >= 1024) 136 | log.warn("The max value of msgLen is 1023, but {} .", msgLen); 137 | int subPkg = isSubPackage ? 1 : 0; 138 | int ret = (msgLen & 0x3FF) | ((enctyptionType << 10) & 0x1C00) | ((subPkg << 13) & 0x2000) 139 | | ((reversed_14_15 << 14) & 0xC000); 140 | return ret & 0xffff; 141 | } 142 | 143 | public byte[] generateMsgHeader(String phone, int msgType, byte[] body, int msgBodyProps, int flowId) 144 | throws Exception { 145 | ByteArrayOutputStream baos = null; 146 | try { 147 | baos = new ByteArrayOutputStream(); 148 | // 1. 消息ID word(16) 149 | baos.write(bitOperator.integerTo2Bytes(msgType)); 150 | // 2. 消息体属性 word(16) 151 | baos.write(bitOperator.integerTo2Bytes(msgBodyProps)); 152 | // 3. 终端手机号 bcd[6] 153 | baos.write(bcd8421Operater.string2Bcd(phone)); 154 | // 4. 消息流水号 word(16),按发送顺序从 0 开始循环累加 155 | baos.write(bitOperator.integerTo2Bytes(flowId)); 156 | // 消息包封装项 此处不予考虑 157 | return baos.toByteArray(); 158 | } finally { 159 | if (baos != null) { 160 | baos.close(); 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/vo/PackageData.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.vo; 2 | 3 | import java.util.Arrays; 4 | 5 | import com.alibaba.fastjson.annotation.JSONField; 6 | 7 | import io.netty.channel.Channel; 8 | 9 | public class PackageData { 10 | 11 | /** 12 | * 16byte 消息头 13 | */ 14 | protected MsgHeader msgHeader; 15 | 16 | // 消息体字节数组 17 | @JSONField(serialize=false) 18 | protected byte[] msgBodyBytes; 19 | 20 | /** 21 | * 校验码 1byte 22 | */ 23 | protected int checkSum; 24 | 25 | @JSONField(serialize=false) 26 | protected Channel channel; 27 | 28 | public MsgHeader getMsgHeader() { 29 | return msgHeader; 30 | } 31 | 32 | public void setMsgHeader(MsgHeader msgHeader) { 33 | this.msgHeader = msgHeader; 34 | } 35 | 36 | public byte[] getMsgBodyBytes() { 37 | return msgBodyBytes; 38 | } 39 | 40 | public void setMsgBodyBytes(byte[] msgBodyBytes) { 41 | this.msgBodyBytes = msgBodyBytes; 42 | } 43 | 44 | public int getCheckSum() { 45 | return checkSum; 46 | } 47 | 48 | public void setCheckSum(int checkSum) { 49 | this.checkSum = checkSum; 50 | } 51 | 52 | public Channel getChannel() { 53 | return channel; 54 | } 55 | 56 | public void setChannel(Channel channel) { 57 | this.channel = channel; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "PackageData [msgHeader=" + msgHeader + ", msgBodyBytes=" + Arrays.toString(msgBodyBytes) + ", checkSum=" 63 | + checkSum + ", address=" + channel + "]"; 64 | } 65 | 66 | public static class MsgHeader { 67 | // 消息ID 68 | protected int msgId; 69 | 70 | /////// ========消息体属性 71 | // byte[2-3] 72 | protected int msgBodyPropsField; 73 | // 消息体长度 74 | protected int msgBodyLength; 75 | // 数据加密方式 76 | protected int encryptionType; 77 | // 是否分包,true==>有消息包封装项 78 | protected boolean hasSubPackage; 79 | // 保留位[14-15] 80 | protected String reservedBit; 81 | /////// ========消息体属性 82 | 83 | // 终端手机号 84 | protected String terminalPhone; 85 | // 流水号 86 | protected int flowId; 87 | 88 | //////// =====消息包封装项 89 | // byte[12-15] 90 | protected int packageInfoField; 91 | // 消息包总数(word(16)) 92 | protected long totalSubPackage; 93 | // 包序号(word(16))这次发送的这个消息包是分包中的第几个消息包, 从 1 开始 94 | protected long subPackageSeq; 95 | //////// =====消息包封装项 96 | 97 | public int getMsgId() { 98 | return msgId; 99 | } 100 | 101 | public void setMsgId(int msgId) { 102 | this.msgId = msgId; 103 | } 104 | 105 | public int getMsgBodyLength() { 106 | return msgBodyLength; 107 | } 108 | 109 | public void setMsgBodyLength(int msgBodyLength) { 110 | this.msgBodyLength = msgBodyLength; 111 | } 112 | 113 | public int getEncryptionType() { 114 | return encryptionType; 115 | } 116 | 117 | public void setEncryptionType(int encryptionType) { 118 | this.encryptionType = encryptionType; 119 | } 120 | 121 | public String getTerminalPhone() { 122 | return terminalPhone; 123 | } 124 | 125 | public void setTerminalPhone(String terminalPhone) { 126 | this.terminalPhone = terminalPhone; 127 | } 128 | 129 | public int getFlowId() { 130 | return flowId; 131 | } 132 | 133 | public void setFlowId(int flowId) { 134 | this.flowId = flowId; 135 | } 136 | 137 | public boolean isHasSubPackage() { 138 | return hasSubPackage; 139 | } 140 | 141 | public void setHasSubPackage(boolean hasSubPackage) { 142 | this.hasSubPackage = hasSubPackage; 143 | } 144 | 145 | public String getReservedBit() { 146 | return reservedBit; 147 | } 148 | 149 | public void setReservedBit(String reservedBit) { 150 | this.reservedBit = reservedBit; 151 | } 152 | 153 | public long getTotalSubPackage() { 154 | return totalSubPackage; 155 | } 156 | 157 | public void setTotalSubPackage(long totalPackage) { 158 | this.totalSubPackage = totalPackage; 159 | } 160 | 161 | public long getSubPackageSeq() { 162 | return subPackageSeq; 163 | } 164 | 165 | public void setSubPackageSeq(long packageSeq) { 166 | this.subPackageSeq = packageSeq; 167 | } 168 | 169 | public int getMsgBodyPropsField() { 170 | return msgBodyPropsField; 171 | } 172 | 173 | public void setMsgBodyPropsField(int msgBodyPropsField) { 174 | this.msgBodyPropsField = msgBodyPropsField; 175 | } 176 | 177 | public void setPackageInfoField(int packageInfoField) { 178 | this.packageInfoField = packageInfoField; 179 | } 180 | 181 | public int getPackageInfoField() { 182 | return packageInfoField; 183 | } 184 | 185 | @Override 186 | public String toString() { 187 | return "MsgHeader [msgId=" + msgId + ", msgBodyPropsField=" + msgBodyPropsField + ", msgBodyLength=" 188 | + msgBodyLength + ", encryptionType=" + encryptionType + ", hasSubPackage=" + hasSubPackage 189 | + ", reservedBit=" + reservedBit + ", terminalPhone=" + terminalPhone + ", flowId=" + flowId 190 | + ", packageInfoField=" + packageInfoField + ", totalSubPackage=" + totalSubPackage 191 | + ", subPackageSeq=" + subPackageSeq + "]"; 192 | } 193 | 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/service/TerminalMsgProcessService.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.service; 2 | 3 | import cn.hylexus.jt808.vo.req.LocationInfoUploadMsg; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import com.alibaba.fastjson.JSON; 8 | 9 | import cn.hylexus.jt808.server.SessionManager; 10 | import cn.hylexus.jt808.service.codec.MsgEncoder; 11 | import cn.hylexus.jt808.vo.PackageData; 12 | import cn.hylexus.jt808.vo.PackageData.MsgHeader; 13 | import cn.hylexus.jt808.vo.Session; 14 | import cn.hylexus.jt808.vo.req.TerminalAuthenticationMsg; 15 | import cn.hylexus.jt808.vo.req.TerminalRegisterMsg; 16 | import cn.hylexus.jt808.vo.resp.ServerCommonRespMsgBody; 17 | import cn.hylexus.jt808.vo.resp.TerminalRegisterMsgRespBody; 18 | 19 | public class TerminalMsgProcessService extends BaseMsgProcessService { 20 | 21 | private final Logger log = LoggerFactory.getLogger(getClass()); 22 | private MsgEncoder msgEncoder; 23 | private SessionManager sessionManager; 24 | 25 | public TerminalMsgProcessService() { 26 | this.msgEncoder = new MsgEncoder(); 27 | this.sessionManager = SessionManager.getInstance(); 28 | } 29 | 30 | public void processRegisterMsg(TerminalRegisterMsg msg) throws Exception { 31 | log.debug("终端注册:{}", JSON.toJSONString(msg, true)); 32 | 33 | final String sessionId = Session.buildId(msg.getChannel()); 34 | Session session = sessionManager.findBySessionId(sessionId); 35 | if (session == null) { 36 | session = Session.buildSession(msg.getChannel(), msg.getMsgHeader().getTerminalPhone()); 37 | } 38 | session.setAuthenticated(true); 39 | session.setTerminalPhone(msg.getMsgHeader().getTerminalPhone()); 40 | sessionManager.put(session.getId(), session); 41 | 42 | TerminalRegisterMsgRespBody respMsgBody = new TerminalRegisterMsgRespBody(); 43 | respMsgBody.setReplyCode(TerminalRegisterMsgRespBody.success); 44 | respMsgBody.setReplyFlowId(msg.getMsgHeader().getFlowId()); 45 | // TODO 鉴权码暂时写死 46 | respMsgBody.setReplyToken("123"); 47 | int flowId = super.getFlowId(msg.getChannel()); 48 | byte[] bs = this.msgEncoder.encode4TerminalRegisterResp(msg, respMsgBody, flowId); 49 | 50 | super.send2Client(msg.getChannel(), bs); 51 | } 52 | 53 | public void processAuthMsg(TerminalAuthenticationMsg msg) throws Exception { 54 | // TODO 暂时每次鉴权都成功 55 | 56 | log.debug("终端鉴权:{}", JSON.toJSONString(msg, true)); 57 | 58 | final String sessionId = Session.buildId(msg.getChannel()); 59 | Session session = sessionManager.findBySessionId(sessionId); 60 | if (session == null) { 61 | session = Session.buildSession(msg.getChannel(), msg.getMsgHeader().getTerminalPhone()); 62 | } 63 | session.setAuthenticated(true); 64 | session.setTerminalPhone(msg.getMsgHeader().getTerminalPhone()); 65 | sessionManager.put(session.getId(), session); 66 | 67 | ServerCommonRespMsgBody respMsgBody = new ServerCommonRespMsgBody(); 68 | respMsgBody.setReplyCode(ServerCommonRespMsgBody.success); 69 | respMsgBody.setReplyFlowId(msg.getMsgHeader().getFlowId()); 70 | respMsgBody.setReplyId(msg.getMsgHeader().getMsgId()); 71 | int flowId = super.getFlowId(msg.getChannel()); 72 | byte[] bs = this.msgEncoder.encode4ServerCommonRespMsg(msg, respMsgBody, flowId); 73 | super.send2Client(msg.getChannel(), bs); 74 | } 75 | 76 | public void processTerminalHeartBeatMsg(PackageData req) throws Exception { 77 | log.debug("心跳信息:{}", JSON.toJSONString(req, true)); 78 | final MsgHeader reqHeader = req.getMsgHeader(); 79 | ServerCommonRespMsgBody respMsgBody = new ServerCommonRespMsgBody(reqHeader.getFlowId(), reqHeader.getMsgId(), 80 | ServerCommonRespMsgBody.success); 81 | int flowId = super.getFlowId(req.getChannel()); 82 | byte[] bs = this.msgEncoder.encode4ServerCommonRespMsg(req, respMsgBody, flowId); 83 | super.send2Client(req.getChannel(), bs); 84 | } 85 | 86 | public void processTerminalLogoutMsg(PackageData req) throws Exception { 87 | log.info("终端注销:{}", JSON.toJSONString(req, true)); 88 | final MsgHeader reqHeader = req.getMsgHeader(); 89 | ServerCommonRespMsgBody respMsgBody = new ServerCommonRespMsgBody(reqHeader.getFlowId(), reqHeader.getMsgId(), 90 | ServerCommonRespMsgBody.success); 91 | int flowId = super.getFlowId(req.getChannel()); 92 | byte[] bs = this.msgEncoder.encode4ServerCommonRespMsg(req, respMsgBody, flowId); 93 | super.send2Client(req.getChannel(), bs); 94 | } 95 | 96 | public void processLocationInfoUploadMsg(LocationInfoUploadMsg req) throws Exception { 97 | log.debug("位置 信息:{}", JSON.toJSONString(req, true)); 98 | final MsgHeader reqHeader = req.getMsgHeader(); 99 | ServerCommonRespMsgBody respMsgBody = new ServerCommonRespMsgBody(reqHeader.getFlowId(), reqHeader.getMsgId(), 100 | ServerCommonRespMsgBody.success); 101 | int flowId = super.getFlowId(req.getChannel()); 102 | byte[] bs = this.msgEncoder.encode4ServerCommonRespMsg(req, respMsgBody, flowId); 103 | super.send2Client(req.getChannel(), bs); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | 83 | 84 | 85 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/service/handler/TCPServerHandler.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.service.handler; 2 | 3 | import cn.hylexus.jt808.vo.req.LocationInfoUploadMsg; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import cn.hylexus.jt808.common.TPMSConsts; 8 | import cn.hylexus.jt808.server.SessionManager; 9 | import cn.hylexus.jt808.service.TerminalMsgProcessService; 10 | import cn.hylexus.jt808.service.codec.MsgDecoder; 11 | import cn.hylexus.jt808.vo.PackageData; 12 | import cn.hylexus.jt808.vo.PackageData.MsgHeader; 13 | import cn.hylexus.jt808.vo.Session; 14 | import cn.hylexus.jt808.vo.req.TerminalAuthenticationMsg; 15 | import cn.hylexus.jt808.vo.req.TerminalRegisterMsg; 16 | import io.netty.buffer.ByteBuf; 17 | import io.netty.channel.ChannelHandlerContext; 18 | import io.netty.channel.ChannelInboundHandlerAdapter; 19 | import io.netty.handler.timeout.IdleState; 20 | import io.netty.handler.timeout.IdleStateEvent; 21 | import io.netty.util.ReferenceCountUtil; 22 | 23 | public class TCPServerHandler extends ChannelInboundHandlerAdapter { // (1) 24 | 25 | private final Logger logger = LoggerFactory.getLogger(getClass()); 26 | 27 | private final SessionManager sessionManager; 28 | private final MsgDecoder decoder; 29 | private TerminalMsgProcessService msgProcessService; 30 | 31 | public TCPServerHandler() { 32 | this.sessionManager = SessionManager.getInstance(); 33 | this.decoder = new MsgDecoder(); 34 | this.msgProcessService = new TerminalMsgProcessService(); 35 | } 36 | 37 | @Override 38 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws InterruptedException { // (2) 39 | try { 40 | ByteBuf buf = (ByteBuf) msg; 41 | if (buf.readableBytes() <= 0) { 42 | // ReferenceCountUtil.safeRelease(msg); 43 | return; 44 | } 45 | 46 | byte[] bs = new byte[buf.readableBytes()]; 47 | 48 | //bs="7e0100002c0200000000150025002c0133373039363054372d54383038000000000000000000000000003033323931373001d4c142383838387b7e".getBytes(); 49 | 50 | buf.readBytes(bs); 51 | 52 | // 字节数据转换为针对于808消息结构的实体类 53 | PackageData pkg = this.decoder.bytes2PackageData(bs); 54 | // 引用channel,以便回送数据给硬件 55 | pkg.setChannel(ctx.channel()); 56 | this.processPackageData(pkg); 57 | } finally { 58 | release(msg); 59 | } 60 | } 61 | 62 | /** 63 | * 64 | * 处理业务逻辑 65 | * 66 | * @param packageData 67 | * 68 | */ 69 | private void processPackageData(PackageData packageData) { 70 | final MsgHeader header = packageData.getMsgHeader(); 71 | 72 | // 1. 终端心跳-消息体为空 ==> 平台通用应答 73 | if (TPMSConsts.msg_id_terminal_heart_beat == header.getMsgId()) { 74 | logger.info(">>>>>[终端心跳],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 75 | try { 76 | this.msgProcessService.processTerminalHeartBeatMsg(packageData); 77 | logger.info("<<<<<[终端心跳],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 78 | } catch (Exception e) { 79 | logger.error("<<<<<[终端心跳]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(), 80 | e.getMessage()); 81 | e.printStackTrace(); 82 | } 83 | } 84 | 85 | // 5. 终端鉴权 ==> 平台通用应答 86 | else if (TPMSConsts.msg_id_terminal_authentication == header.getMsgId()) { 87 | logger.info(">>>>>[终端鉴权],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 88 | try { 89 | TerminalAuthenticationMsg authenticationMsg = new TerminalAuthenticationMsg(packageData); 90 | this.msgProcessService.processAuthMsg(authenticationMsg); 91 | logger.info("<<<<<[终端鉴权],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 92 | } catch (Exception e) { 93 | logger.error("<<<<<[终端鉴权]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(), 94 | e.getMessage()); 95 | e.printStackTrace(); 96 | } 97 | } 98 | // 6. 终端注册 ==> 终端注册应答 99 | else if (TPMSConsts.msg_id_terminal_register == header.getMsgId()) { 100 | logger.info(">>>>>[终端注册],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 101 | try { 102 | TerminalRegisterMsg msg = this.decoder.toTerminalRegisterMsg(packageData); 103 | this.msgProcessService.processRegisterMsg(msg); 104 | logger.info("<<<<<[终端注册],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 105 | } catch (Exception e) { 106 | logger.error("<<<<<[终端注册]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(), 107 | e.getMessage()); 108 | e.printStackTrace(); 109 | } 110 | } 111 | // 7. 终端注销(终端注销数据消息体为空) ==> 平台通用应答 112 | else if (TPMSConsts.msg_id_terminal_log_out == header.getMsgId()) { 113 | logger.info(">>>>>[终端注销],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 114 | try { 115 | this.msgProcessService.processTerminalLogoutMsg(packageData); 116 | logger.info("<<<<<[终端注销],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 117 | } catch (Exception e) { 118 | logger.error("<<<<<[终端注销]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(), 119 | e.getMessage()); 120 | e.printStackTrace(); 121 | } 122 | } 123 | // 3. 位置信息汇报 ==> 平台通用应答 124 | else if (TPMSConsts.msg_id_terminal_location_info_upload == header.getMsgId()) { 125 | logger.info(">>>>>[位置信息],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 126 | try { 127 | LocationInfoUploadMsg locationInfoUploadMsg = this.decoder.toLocationInfoUploadMsg(packageData); 128 | System.out.println(locationInfoUploadMsg); 129 | this.msgProcessService.processLocationInfoUploadMsg(locationInfoUploadMsg); 130 | logger.info("<<<<<[位置信息],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId()); 131 | } catch (Exception e) { 132 | logger.error("<<<<<[位置信息]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(), 133 | e.getMessage()); 134 | e.printStackTrace(); 135 | } 136 | } 137 | // 其他情况 138 | else { 139 | logger.error(">>>>>>[未知消息类型],phone={},msgId={},package={}", header.getTerminalPhone(), header.getMsgId(), 140 | packageData); 141 | } 142 | } 143 | 144 | @Override 145 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4) 146 | logger.error("发生异常:{}", cause.getMessage()); 147 | cause.printStackTrace(); 148 | } 149 | 150 | @Override 151 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 152 | Session session = Session.buildSession(ctx.channel()); 153 | sessionManager.put(session.getId(), session); 154 | logger.debug("终端连接:{}", session); 155 | } 156 | 157 | @Override 158 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 159 | final String sessionId = ctx.channel().id().asLongText(); 160 | Session session = sessionManager.findBySessionId(sessionId); 161 | this.sessionManager.removeBySessionId(sessionId); 162 | logger.debug("终端断开连接:{}", session); 163 | ctx.channel().close(); 164 | // ctx.close(); 165 | } 166 | 167 | @Override 168 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 169 | if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) { 170 | IdleStateEvent event = (IdleStateEvent) evt; 171 | if (event.state() == IdleState.READER_IDLE) { 172 | Session session = this.sessionManager.removeBySessionId(Session.buildId(ctx.channel())); 173 | logger.error("服务器主动断开连接:{}", session); 174 | ctx.close(); 175 | } 176 | } 177 | } 178 | 179 | private void release(Object msg) { 180 | try { 181 | ReferenceCountUtil.release(msg); 182 | } catch (Exception e) { 183 | e.printStackTrace(); 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/resources/log4j.dtd: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 74 | 78 | 79 | 80 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | 99 | 100 | 103 | 104 | 105 | 109 | 110 | 111 | 112 | 113 | 117 | 118 | 119 | 120 | 124 | 125 | 126 | 127 | 128 | 129 | 134 | 135 | 136 | 137 | 138 | 143 | 144 | 145 | 146 | 148 | 149 | 150 | 152 | 153 | 154 | 157 | 158 | 159 | 160 | 164 | 165 | 166 | 169 | 170 | 171 | 174 | 175 | 176 | 180 | 181 | 182 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 203 | 204 | 205 | 206 | 208 | 209 | 210 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 230 | 231 | 232 | 233 | 234 | 238 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/service/codec/MsgDecoder.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.service.codec; 2 | 3 | import cn.hylexus.jt808.vo.req.LocationInfoUploadMsg; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import cn.hylexus.jt808.common.TPMSConsts; 8 | import cn.hylexus.jt808.util.BCD8421Operater; 9 | import cn.hylexus.jt808.util.BitOperator; 10 | import cn.hylexus.jt808.vo.PackageData; 11 | import cn.hylexus.jt808.vo.PackageData.MsgHeader; 12 | import cn.hylexus.jt808.vo.req.TerminalRegisterMsg; 13 | import cn.hylexus.jt808.vo.req.TerminalRegisterMsg.TerminalRegInfo; 14 | 15 | public class MsgDecoder { 16 | 17 | private static final Logger log = LoggerFactory.getLogger(MsgDecoder.class); 18 | 19 | private BitOperator bitOperator; 20 | private BCD8421Operater bcd8421Operater; 21 | 22 | public MsgDecoder() { 23 | this.bitOperator = new BitOperator(); 24 | this.bcd8421Operater = new BCD8421Operater(); 25 | } 26 | 27 | public PackageData bytes2PackageData(byte[] data) { 28 | PackageData ret = new PackageData(); 29 | 30 | // 0. 终端套接字地址信息 31 | // ret.setChannel(msg.getChannel()); 32 | 33 | // 1. 16byte 或 12byte 消息头 34 | MsgHeader msgHeader = this.parseMsgHeaderFromBytes(data); 35 | ret.setMsgHeader(msgHeader); 36 | 37 | int msgBodyByteStartIndex = 12; 38 | // 2. 消息体 39 | // 有子包信息,消息体起始字节后移四个字节:消息包总数(word(16))+包序号(word(16)) 40 | if (msgHeader.isHasSubPackage()) { 41 | msgBodyByteStartIndex = 16; 42 | } 43 | 44 | byte[] tmp = new byte[msgHeader.getMsgBodyLength()]; 45 | System.arraycopy(data, msgBodyByteStartIndex, tmp, 0, tmp.length); 46 | ret.setMsgBodyBytes(tmp); 47 | 48 | // 3. 去掉分隔符之后,最后一位就是校验码 49 | // int checkSumInPkg = 50 | // this.bitOperator.oneByteToInteger(data[data.length - 1]); 51 | int checkSumInPkg = data[data.length - 1]; 52 | int calculatedCheckSum = this.bitOperator.getCheckSum4JT808(data, 0, data.length - 1); 53 | ret.setCheckSum(checkSumInPkg); 54 | if (checkSumInPkg != calculatedCheckSum) { 55 | log.warn("检验码不一致,msgid:{},pkg:{},calculated:{}", msgHeader.getMsgId(), checkSumInPkg, calculatedCheckSum); 56 | } 57 | return ret; 58 | } 59 | 60 | private MsgHeader parseMsgHeaderFromBytes(byte[] data) { 61 | MsgHeader msgHeader = new MsgHeader(); 62 | 63 | // 1. 消息ID word(16) 64 | // byte[] tmp = new byte[2]; 65 | // System.arraycopy(data, 0, tmp, 0, 2); 66 | // msgHeader.setMsgId(this.bitOperator.twoBytesToInteger(tmp)); 67 | msgHeader.setMsgId(this.parseIntFromBytes(data, 0, 2)); 68 | 69 | // 2. 消息体属性 word(16)=================> 70 | // System.arraycopy(data, 2, tmp, 0, 2); 71 | // int msgBodyProps = this.bitOperator.twoBytesToInteger(tmp); 72 | int msgBodyProps = this.parseIntFromBytes(data, 2, 2); 73 | msgHeader.setMsgBodyPropsField(msgBodyProps); 74 | // [ 0-9 ] 0000,0011,1111,1111(3FF)(消息体长度) 75 | msgHeader.setMsgBodyLength(msgBodyProps & 0x3ff); 76 | // [10-12] 0001,1100,0000,0000(1C00)(加密类型) 77 | msgHeader.setEncryptionType((msgBodyProps & 0x1c00) >> 10); 78 | // [ 13_ ] 0010,0000,0000,0000(2000)(是否有子包) 79 | msgHeader.setHasSubPackage(((msgBodyProps & 0x2000) >> 13) == 1); 80 | // [14-15] 1100,0000,0000,0000(C000)(保留位) 81 | msgHeader.setReservedBit(((msgBodyProps & 0xc000) >> 14) + ""); 82 | // 消息体属性 word(16)<================= 83 | 84 | // 3. 终端手机号 bcd[6] 85 | // tmp = new byte[6]; 86 | // System.arraycopy(data, 4, tmp, 0, 6); 87 | // msgHeader.setTerminalPhone(this.bcd8421Operater.bcd2String(tmp)); 88 | msgHeader.setTerminalPhone(this.parseBcdStringFromBytes(data, 4, 6)); 89 | 90 | // 4. 消息流水号 word(16) 按发送顺序从 0 开始循环累加 91 | // tmp = new byte[2]; 92 | // System.arraycopy(data, 10, tmp, 0, 2); 93 | // msgHeader.setFlowId(this.bitOperator.twoBytesToInteger(tmp)); 94 | msgHeader.setFlowId(this.parseIntFromBytes(data, 10, 2)); 95 | 96 | // 5. 消息包封装项 97 | // 有子包信息 98 | if (msgHeader.isHasSubPackage()) { 99 | // 消息包封装项字段 100 | msgHeader.setPackageInfoField(this.parseIntFromBytes(data, 12, 4)); 101 | // byte[0-1] 消息包总数(word(16)) 102 | // tmp = new byte[2]; 103 | // System.arraycopy(data, 12, tmp, 0, 2); 104 | // msgHeader.setTotalSubPackage(this.bitOperator.twoBytesToInteger(tmp)); 105 | msgHeader.setTotalSubPackage(this.parseIntFromBytes(data, 12, 2)); 106 | 107 | // byte[2-3] 包序号(word(16)) 从 1 开始 108 | // tmp = new byte[2]; 109 | // System.arraycopy(data, 14, tmp, 0, 2); 110 | // msgHeader.setSubPackageSeq(this.bitOperator.twoBytesToInteger(tmp)); 111 | msgHeader.setSubPackageSeq(this.parseIntFromBytes(data, 14, 2)); 112 | } 113 | return msgHeader; 114 | } 115 | 116 | protected String parseStringFromBytes(byte[] data, int startIndex, int lenth) { 117 | return this.parseStringFromBytes(data, startIndex, lenth, null); 118 | } 119 | 120 | private String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) { 121 | try { 122 | byte[] tmp = new byte[lenth]; 123 | System.arraycopy(data, startIndex, tmp, 0, lenth); 124 | return new String(tmp, TPMSConsts.string_charset); 125 | } catch (Exception e) { 126 | log.error("解析字符串出错:{}", e.getMessage()); 127 | e.printStackTrace(); 128 | return defaultVal; 129 | } 130 | } 131 | 132 | private String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) { 133 | return this.parseBcdStringFromBytes(data, startIndex, lenth, null); 134 | } 135 | 136 | private String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) { 137 | try { 138 | byte[] tmp = new byte[lenth]; 139 | System.arraycopy(data, startIndex, tmp, 0, lenth); 140 | return this.bcd8421Operater.bcd2String(tmp); 141 | } catch (Exception e) { 142 | log.error("解析BCD(8421码)出错:{}", e.getMessage()); 143 | e.printStackTrace(); 144 | return defaultVal; 145 | } 146 | } 147 | 148 | private int parseIntFromBytes(byte[] data, int startIndex, int length) { 149 | return this.parseIntFromBytes(data, startIndex, length, 0); 150 | } 151 | 152 | private int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) { 153 | try { 154 | // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃 155 | final int len = length > 4 ? 4 : length; 156 | byte[] tmp = new byte[len]; 157 | System.arraycopy(data, startIndex, tmp, 0, len); 158 | return bitOperator.byteToInteger(tmp); 159 | } catch (Exception e) { 160 | log.error("解析整数出错:{}", e.getMessage()); 161 | e.printStackTrace(); 162 | return defaultVal; 163 | } 164 | } 165 | 166 | public TerminalRegisterMsg toTerminalRegisterMsg(PackageData packageData) { 167 | TerminalRegisterMsg ret = new TerminalRegisterMsg(packageData); 168 | byte[] data = ret.getMsgBodyBytes(); 169 | 170 | TerminalRegInfo body = new TerminalRegInfo(); 171 | 172 | // 1. byte[0-1] 省域ID(WORD) 173 | // 设备安装车辆所在的省域,省域ID采用GB/T2260中规定的行政区划代码6位中前两位 174 | // 0保留,由平台取默认值 175 | body.setProvinceId(this.parseIntFromBytes(data, 0, 2)); 176 | 177 | // 2. byte[2-3] 设备安装车辆所在的市域或县域,市县域ID采用GB/T2260中规定的行 政区划代码6位中后四位 178 | // 0保留,由平台取默认值 179 | body.setCityId(this.parseIntFromBytes(data, 2, 2)); 180 | 181 | // 3. byte[4-8] 制造商ID(BYTE[5]) 5 个字节,终端制造商编码 182 | // byte[] tmp = new byte[5]; 183 | body.setManufacturerId(this.parseStringFromBytes(data, 4, 5)); 184 | 185 | // 4. byte[9-16] 终端型号(BYTE[8]) 八个字节, 此终端型号 由制造商自行定义 位数不足八位的,补空格。 186 | body.setTerminalType(this.parseStringFromBytes(data, 9, 8)); 187 | 188 | // 5. byte[17-23] 终端ID(BYTE[7]) 七个字节, 由大写字母 和数字组成, 此终端 ID由制造 商自行定义 189 | body.setTerminalId(this.parseStringFromBytes(data, 17, 7)); 190 | 191 | // 6. byte[24] 车牌颜色(BYTE) 车牌颜 色按照JT/T415-2006 中5.4.12 的规定 192 | body.setLicensePlateColor(this.parseIntFromBytes(data, 24, 1)); 193 | 194 | // 7. byte[25-x] 车牌(STRING) 公安交 通管理部门颁 发的机动车号牌 195 | body.setLicensePlate(this.parseStringFromBytes(data, 25, data.length - 25)); 196 | 197 | ret.setTerminalRegInfo(body); 198 | return ret; 199 | } 200 | 201 | 202 | public LocationInfoUploadMsg toLocationInfoUploadMsg(PackageData packageData) { 203 | LocationInfoUploadMsg ret = new LocationInfoUploadMsg(packageData); 204 | final byte[] data = ret.getMsgBodyBytes(); 205 | 206 | // 1. byte[0-3] 报警标志(DWORD(32)) 207 | ret.setWarningFlagField(this.parseIntFromBytes(data, 0, 3)); 208 | // 2. byte[4-7] 状态(DWORD(32)) 209 | ret.setStatusField(this.parseIntFromBytes(data, 4, 4)); 210 | // 3. byte[8-11] 纬度(DWORD(32)) 以度为单位的纬度值乘以10^6,精确到百万分之一度 211 | // String weidu=this.parseBcdStringFromBytes(data, 8, 4); 212 | //float x = Integer.parseInt(weidu,16); 213 | ret.setLatitude(this.parseIntFromBytes(data, 8, 4)/1000000); //parseFloatFromBytes 改动的 214 | // 4. byte[12-15] 经度(DWORD(32)) 以度为单位的经度值乘以10^6,精确到百万分之一度 215 | ret.setLongitude(this.parseIntFromBytes(data, 12, 4)/1000000); 216 | //ret.setLongitude(y); 217 | // 5. byte[16-17] 高程(WORD(16)) 海拔高度,单位为米( m) 218 | ret.setElevation(this.parseIntFromBytes(data, 16, 2)); 219 | // byte[18-19] 速度(WORD) 1/10km/h 220 | ret.setSpeed(this.parseFloatFromBytes(data, 18, 2)); 221 | // byte[20-21] 方向(WORD) 0-359,正北为 0,顺时针 222 | ret.setDirection(this.parseIntFromBytes(data, 20, 2)); 223 | // byte[22-x] 时间(BCD[6]) YY-MM-DD-hh-mm-ss 224 | // GMT+8 时间,本标准中之后涉及的时间均采用此时区 225 | // ret.setTime(this.par); 226 | 227 | byte[] tmp = new byte[6]; 228 | System.arraycopy(data, 22, tmp, 0, 6); 229 | String time = this.parseBcdStringFromBytes(data, 22, 6); 230 | return ret; 231 | } 232 | 233 | private float parseFloatFromBytes(byte[] data, int startIndex, int length) { 234 | return this.parseFloatFromBytes(data, startIndex, length, 0f); 235 | } 236 | 237 | private float parseFloatFromBytes(byte[] data, int startIndex, int length, float defaultVal) { 238 | try { 239 | // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃 240 | final int len = length > 4 ? 4 : length; 241 | byte[] tmp = new byte[len]; 242 | System.arraycopy(data, startIndex, tmp, 0, len); 243 | //两位的长度 244 | return bitOperator.byte2Float2(tmp); 245 | } catch (Exception e) { 246 | log.error("解析浮点数出错:{}", e.getMessage()); 247 | e.printStackTrace(); 248 | return defaultVal; 249 | } 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /jt808-tcp-netty/src/main/java/cn/hylexus/jt808/util/BitOperator.java: -------------------------------------------------------------------------------- 1 | package cn.hylexus.jt808.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class BitOperator { 7 | 8 | /** 9 | * 把一个整形该为byte 10 | * 11 | * @param value 12 | * @return 13 | * @throws Exception 14 | */ 15 | public byte integerTo1Byte(int value) { 16 | return (byte) (value & 0xFF); 17 | } 18 | 19 | /** 20 | * 把一个整形该为1位的byte数组 21 | * 22 | * @param value 23 | * @return 24 | * @throws Exception 25 | */ 26 | public byte[] integerTo1Bytes(int value) { 27 | byte[] result = new byte[1]; 28 | result[0] = (byte) (value & 0xFF); 29 | return result; 30 | } 31 | 32 | /** 33 | * 把一个整形改为2位的byte数组 34 | * 35 | * @param value 36 | * @return 37 | * @throws Exception 38 | */ 39 | public byte[] integerTo2Bytes(int value) { 40 | byte[] result = new byte[2]; 41 | result[0] = (byte) ((value >>> 8) & 0xFF); 42 | result[1] = (byte) (value & 0xFF); 43 | return result; 44 | } 45 | 46 | /** 47 | * 把一个整形改为3位的byte数组 48 | * 49 | * @param value 50 | * @return 51 | * @throws Exception 52 | */ 53 | public byte[] integerTo3Bytes(int value) { 54 | byte[] result = new byte[3]; 55 | result[0] = (byte) ((value >>> 16) & 0xFF); 56 | result[1] = (byte) ((value >>> 8) & 0xFF); 57 | result[2] = (byte) (value & 0xFF); 58 | return result; 59 | } 60 | 61 | /** 62 | * 把一个整形改为4位的byte数组 63 | * 64 | * @param value 65 | * @return 66 | * @throws Exception 67 | */ 68 | public byte[] integerTo4Bytes(int value){ 69 | byte[] result = new byte[4]; 70 | result[0] = (byte) ((value >>> 24) & 0xFF); 71 | result[1] = (byte) ((value >>> 16) & 0xFF); 72 | result[2] = (byte) ((value >>> 8) & 0xFF); 73 | result[3] = (byte) (value & 0xFF); 74 | return result; 75 | } 76 | 77 | /** 78 | * 把byte[]转化位整形,通常为指令用 79 | * 80 | * @param value 81 | * @return 82 | * @throws Exception 83 | */ 84 | public int byteToInteger(byte[] value) { 85 | int result; 86 | if (value.length == 1) { 87 | result = oneByteToInteger(value[0]); 88 | } else if (value.length == 2) { 89 | result = twoBytesToInteger(value); 90 | } else if (value.length == 3) { 91 | result = threeBytesToInteger(value); 92 | } else if (value.length == 4) { 93 | result = fourBytesToInteger(value); 94 | } else { 95 | result = fourBytesToInteger(value); 96 | } 97 | return result; 98 | } 99 | 100 | /** 101 | * 把一个byte转化位整形,通常为指令用 102 | * 103 | * @param value 104 | * @return 105 | * @throws Exception 106 | */ 107 | public int oneByteToInteger(byte value) { 108 | return (int) value & 0xFF; 109 | } 110 | 111 | /** 112 | * 把一个2位的数组转化位整形 113 | * 114 | * @param value 115 | * @return 116 | * @throws Exception 117 | */ 118 | public int twoBytesToInteger(byte[] value) { 119 | // if (value.length < 2) { 120 | // throw new Exception("Byte array too short!"); 121 | // } 122 | int temp0 = value[0] & 0xFF; 123 | int temp1 = value[1] & 0xFF; 124 | return ((temp0 << 8) + temp1); 125 | } 126 | 127 | /** 128 | * 把一个3位的数组转化位整形 129 | * 130 | * @param value 131 | * @return 132 | * @throws Exception 133 | */ 134 | public int threeBytesToInteger(byte[] value) { 135 | int temp0 = value[0] & 0xFF; 136 | int temp1 = value[1] & 0xFF; 137 | int temp2 = value[2] & 0xFF; 138 | return ((temp0 << 16) + (temp1 << 8) + temp2); 139 | } 140 | 141 | /** 142 | * 把一个4位的数组转化位整形,通常为指令用 143 | * 144 | * @param value 145 | * @return 146 | * @throws Exception 147 | */ 148 | public int fourBytesToInteger(byte[] value) { 149 | // if (value.length < 4) { 150 | // throw new Exception("Byte array too short!"); 151 | // } 152 | int temp0 = value[0] & 0xFF; 153 | int temp1 = value[1] & 0xFF; 154 | int temp2 = value[2] & 0xFF; 155 | int temp3 = value[3] & 0xFF; 156 | return ((temp0 << 24) + (temp1 << 16) + (temp2 << 8) + temp3); 157 | } 158 | 159 | /** 160 | * 把一个4位的数组转化位整形 161 | * 162 | * @param value 163 | * @return 164 | * @throws Exception 165 | */ 166 | public long fourBytesToLong(byte[] value) throws Exception { 167 | // if (value.length < 4) { 168 | // throw new Exception("Byte array too short!"); 169 | // } 170 | int temp0 = value[0] & 0xFF; 171 | int temp1 = value[1] & 0xFF; 172 | int temp2 = value[2] & 0xFF; 173 | int temp3 = value[3] & 0xFF; 174 | return (((long) temp0 << 24) + (temp1 << 16) + (temp2 << 8) + temp3); 175 | } 176 | 177 | /** 178 | * 把一个数组转化长整形 179 | * 180 | * @param value 181 | * @return 182 | * @throws Exception 183 | */ 184 | public long bytes2Long(byte[] value) { 185 | long result = 0; 186 | int len = value.length; 187 | int temp; 188 | for (int i = 0; i < len; i++) { 189 | temp = (len - 1 - i) * 8; 190 | if (temp == 0) { 191 | result += (value[i] & 0x0ff); 192 | } else { 193 | result += (value[i] & 0x0ff) << temp; 194 | } 195 | } 196 | return result; 197 | } 198 | 199 | /** 200 | * 把一个长整形改为byte数组 201 | * 202 | * @param value 203 | * @return 204 | * @throws Exception 205 | */ 206 | public byte[] longToBytes(long value){ 207 | return longToBytes(value, 8); 208 | } 209 | 210 | /** 211 | * 把一个长整形改为byte数组 212 | * 213 | * @param value 214 | * @return 215 | * @throws Exception 216 | */ 217 | public byte[] longToBytes(long value, int len) { 218 | byte[] result = new byte[len]; 219 | int temp; 220 | for (int i = 0; i < len; i++) { 221 | temp = (len - 1 - i) * 8; 222 | if (temp == 0) { 223 | result[i] += (value & 0x0ff); 224 | } else { 225 | result[i] += (value >>> temp) & 0x0ff; 226 | } 227 | } 228 | return result; 229 | } 230 | 231 | /** 232 | * 得到一个消息ID 233 | * 234 | * @return 235 | * @throws Exception 236 | */ 237 | public byte[] generateTransactionID() throws Exception { 238 | byte[] id = new byte[16]; 239 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 0, 2); 240 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 2, 2); 241 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 4, 2); 242 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 6, 2); 243 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 8, 2); 244 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 10, 2); 245 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 12, 2); 246 | System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536)), 0, id, 14, 2); 247 | return id; 248 | } 249 | 250 | /** 251 | * 把IP拆分位int数组 252 | * 253 | * @param ip 254 | * @return 255 | * @throws Exception 256 | */ 257 | public int[] getIntIPValue(String ip) throws Exception { 258 | String[] sip = ip.split("[.]"); 259 | // if (sip.length != 4) { 260 | // throw new Exception("error IPAddress"); 261 | // } 262 | int[] intIP = { Integer.parseInt(sip[0]), Integer.parseInt(sip[1]), Integer.parseInt(sip[2]), 263 | Integer.parseInt(sip[3]) }; 264 | return intIP; 265 | } 266 | 267 | /** 268 | * 把byte类型IP地址转化位字符串 269 | * 270 | * @param address 271 | * @return 272 | * @throws Exception 273 | */ 274 | public String getStringIPValue(byte[] address) throws Exception { 275 | int first = this.oneByteToInteger(address[0]); 276 | int second = this.oneByteToInteger(address[1]); 277 | int third = this.oneByteToInteger(address[2]); 278 | int fourth = this.oneByteToInteger(address[3]); 279 | 280 | return first + "." + second + "." + third + "." + fourth; 281 | } 282 | 283 | /** 284 | * 合并字节数组 285 | * 286 | * @param first 287 | * @param rest 288 | * @return 289 | */ 290 | public byte[] concatAll(byte[] first, byte[]... rest) { 291 | int totalLength = first.length; 292 | for (byte[] array : rest) { 293 | if (array != null) { 294 | totalLength += array.length; 295 | } 296 | } 297 | byte[] result = Arrays.copyOf(first, totalLength); 298 | int offset = first.length; 299 | for (byte[] array : rest) { 300 | if (array != null) { 301 | System.arraycopy(array, 0, result, offset, array.length); 302 | offset += array.length; 303 | } 304 | } 305 | return result; 306 | } 307 | 308 | /** 309 | * 合并字节数组 310 | * 311 | * @param rest 312 | * @return 313 | */ 314 | public byte[] concatAll(List rest) { 315 | int totalLength = 0; 316 | for (byte[] array : rest) { 317 | if (array != null) { 318 | totalLength += array.length; 319 | } 320 | } 321 | byte[] result = new byte[totalLength]; 322 | int offset = 0; 323 | for (byte[] array : rest) { 324 | if (array != null) { 325 | System.arraycopy(array, 0, result, offset, array.length); 326 | offset += array.length; 327 | } 328 | } 329 | return result; 330 | } 331 | 332 | public float byte2Float(byte[] bs) { 333 | return Float.intBitsToFloat( 334 | (((bs[3] & 0xFF) << 24) + ((bs[2] & 0xFF) << 16) + ((bs[1] & 0xFF) << 8) + (bs[0] & 0xFF))); 335 | } 336 | public float byte2Float2(byte[] bs) { 337 | return Float.intBitsToFloat( 338 | (((bs[1] & 0xFF) << 8) + (bs[0] & 0xFF))); 339 | } 340 | 341 | public float byteBE2Float(byte[] bytes) { 342 | int l; 343 | l = bytes[0]; 344 | l &= 0xff; 345 | l |= ((long) bytes[1] << 8); 346 | l &= 0xffff; 347 | l |= ((long) bytes[2] << 16); 348 | l &= 0xffffff; 349 | l |= ((long) bytes[3] << 24); 350 | return Float.intBitsToFloat(l); 351 | } 352 | 353 | public int getCheckSum4JT808(byte[] bs, int start, int end) { 354 | if (start < 0 || end > bs.length) 355 | throw new ArrayIndexOutOfBoundsException("getCheckSum4JT808 error : index out of bounds(start=" + start 356 | + ",end=" + end + ",bytes length=" + bs.length + ")"); 357 | int cs = 0; 358 | for (int i = start; i < end; i++) { 359 | cs ^= bs[i]; 360 | } 361 | return cs; 362 | } 363 | 364 | public int getBitRange(int number, int start, int end) { 365 | if (start < 0) 366 | throw new IndexOutOfBoundsException("min index is 0,but start = " + start); 367 | if (end >= Integer.SIZE) 368 | throw new IndexOutOfBoundsException("max index is " + (Integer.SIZE - 1) + ",but end = " + end); 369 | 370 | return (number << Integer.SIZE - (end + 1)) >>> Integer.SIZE - (end - start + 1); 371 | } 372 | 373 | public int getBitAt(int number, int index) { 374 | if (index < 0) 375 | throw new IndexOutOfBoundsException("min index is 0,but " + index); 376 | if (index >= Integer.SIZE) 377 | throw new IndexOutOfBoundsException("max index is " + (Integer.SIZE - 1) + ",but " + index); 378 | 379 | return ((1 << index) & number) >> index; 380 | } 381 | 382 | public int getBitAtS(int number, int index) { 383 | String s = Integer.toBinaryString(number); 384 | return Integer.parseInt(s.charAt(index) + ""); 385 | } 386 | 387 | @Deprecated 388 | public int getBitRangeS(int number, int start, int end) { 389 | String s = Integer.toBinaryString(number); 390 | StringBuilder sb = new StringBuilder(s); 391 | while (sb.length() < Integer.SIZE) { 392 | sb.insert(0, "0"); 393 | } 394 | String tmp = sb.reverse().substring(start, end + 1); 395 | sb = new StringBuilder(tmp); 396 | return Integer.parseInt(sb.reverse().toString(), 2); 397 | } 398 | } 399 | --------------------------------------------------------------------------------