没错,我把这个协议(JT/T 905.4-2014)实现了!

第一次遇到和客户做数据对接,客户不需要提供任何接口的场景……

一、什么是协议?

905.4-2014协议,是交通运输部公路科学研究院起草定制的一个协议标准,它也是基于TCP之上的一个应用层传输协议。协议详情,请参考此文

谈谈我对这个协议的理解。

首先,我们知道,互联网的数据是由0和1构成的,我们在浏览器中能看到对应的数据,是因为浏览器接收的数据遵循了一定的规范,专业点讲就是协议。http协议就是这样的一个协议,它把传输在网络中的数据变得具有极高的可读性。

  • 互联网应web项目大多数是基于该协议实现的;
  • HTTP协议(超文本传输协议HyperText Transfer Protocol),它是基于TCP协议的应用层传输协议,简单来说就是客户端和服务端进行数据传输的一种规则。
  • 905.4-2014协议,是交通运输部公路科学研究院起草定制的一个协议标准,它也是基于TCP之上的一个应用层传输协议。

下面是协议层从底层至顶层的一个模型图:

二、905.4协议约定内容

  • 该协议在通信方式、安全认证进行了约定;
  • 协议对起始位、消息头、消息体、校验位、结束位进行了约定。
  • 不同业务消息具有不同的业务标识与消息结构,数据解析要按位进行。

统一的数据结构

两个平台之间进行数据交换时,所交换的消息的数据结构由五部分组成。

基本数据类型

通信方式

905.4王国是个纪律严明的国度,长久以来,便形成了有问必答的一个交流方式。不像人类那么复杂,会因为情绪的问题刻意避开问题,显得那么不礼貌。

这个王国有总是会分为两个派系,Server(上级平台)和Client(下级平台)。一个无休止的问,一个无休止的答。

上级平台与下级平台之间应采用面向连接的链路通信方式,具体要求如下:

  • 上下级平台间通信方式采用TCP协议长连接方式;
  • 上级平合作为服务器端,提供服务的IP地址、端口号以及用户名、密码等信息,供下级平台接入;
  • 下级平台作为客户端,向上级平台发起建立链路连接请求,链路成功建立后上下级平台通过该链路进行数据通信;
  • 通信链路通过其中的TCP客户端方发送链路保持数据包检测链路连接状态,实现链路的可靠连接。

安全认证

上级平台对下级平台安全验证流程应遵循以下规定:

  • 上级平台为下级平台分配相应的接人码、接人用户名、密码以及数据加解密相关参数;
  • 下级平台与上级平台连接时,发送“登录请求”消息,上级平台收到下级平台连接请求后,首先验证请求的IP地址,如果请求IP地址与约定的接入IP地址不一致,则返回验证失败结果;其次,上级平台对下级平台的接人码、用户名以及密码进行验证,根据验证的结果向下级平台返回相应的结果值;
  • 上下级平台间的数据传输宜采用加密模式传输,实现对传输数据的即时加密。

多种业务功能

  1. 上级平台向 下级平台发送的请求消息,一般以“DOWN”开头,以后缀_REQ结尾;而下级平台向上级平台发送的请求消息一般以“UP”开头,以后缀_REQ结尾;
  2. 当上下级平台之间有应答消息情况下,应答消息可继续沿用对应的请求消息开头标识符,而通过后缀_RSP来标识绪尾。

下表列举了在905.4协议中可以实现的一些业务功能。

消息种类业务数据类型名称业务数据类型标识数值
链路管理类链路登录请求消息UP_CONNECT_REQ0x2001
链路管理类链路登录应答消息UP_CONNECT_RSP0x2002
链路管理类链路注销请求消息UP_CONNECT_REQ0x2003
链路管理类链路注销应答消息UP_DISCONNECT_RSP0x2004
链路管理类链路连接保持请求消息UP_MINKTEST_REQ0x2005
链路管理类链路连接保持应答消息UP_LINKTEST_RSP0x2006
链路管理类链路断开通知消息UP_DISCONNECT_INFORM0x2007
链路管理类下级平台主动关闭链路通知消息UP_CLOSELINK_INFORM0x2008
链路管理类上级平台主动关闭链路通知消息DOWN_CLOSELINK_INFORM0x9008
车辆动态信息交换类链路动态信息交换消息UP_EXG_MSG0x2100
信息统计类统计信息交换消息UP_SUM_MSG0x2200
静态信息交换类链路静态信息交换消息UP_BASE_MSG0x2300

核心流程设计

三、技术要点

  • 使用NIO Socket编程,实现高性能多线程消息处理;
  • 使用模板设计模式,实现高度可扩展的业务框架。
  • 因NIO性能问题,最终使用了现在大火的netty对服务端进行了重构。
    /**
     * 返回应答消息
     * @param requestBytes
     * @return
     */
    public static byte[] buildResponse(byte[] requestBytes,SocketChannel clientChannel){
        log.info("正在解析请求数据:" + ByteUtil.bytesToHex(requestBytes));
        //检查请求的登录状态
        AuthHelper authHelper = SpringContextUtils.getBean(AuthHelper.class);
        HashMap<String, Object> headerMap = LocationParserHelper.getHeaderMap(requestBytes);
        boolean checkLogin = authHelper.checkLogin(headerMap);

        int requestMsgId = MapUtil.getInt(headerMap, "MSG_ID");
        DataParserWorker dataParserWorker;
        MsgEnum msgEnum = MsgEnum.getMsgEnum(requestMsgId);

        //如果交换类消息,则需要从消息体中确认消息的子类型
        if (MsgEnum.UP_EXG_MSG.equals(msgEnum)){
            byte[] dataTypeArr = ArrayUtils.subarray(requestBytes, HEADER_LENGTH, 25);
            msgEnum = MsgEnum.getMsgEnum(ByteUtil.toShort(dataTypeArr));
            if(msgEnum == null){
                log.warn("交换扩展子业务,本系统不作处理……");
                return new byte[0];
            }
            log.info("解析子业务成功:" + msgEnum.getDescp());
        }

        if (checkLogin || MsgEnum.UP_CONNECTION_REQ.equals(msgEnum) ) {
            switch (msgEnum) {
                case UP_CONNECTION_REQ:
                    String ip = null;
                    try {
                        ip = clientChannel.getRemoteAddress().toString().replace("/","");
                        ip = ip.split(":")[0];
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("IP地址不正确");
                    }
                    dataParserWorker = new AuthWorker(requestBytes,ip);
                    break;
                //车辆运营数据上报
                case UP_EXG_MSG_REAL_RUN_REQ:
                    dataParserWorker = new BusinessReportWorker(requestBytes);
                    break;
                case UP_EXG_MSG_REAL_L0CATION_REQ://定位数据采集
                    dataParserWorker = new LocationReportWorker(requestBytes);
                    break;
                case UP_LINKTEST_REQ://链路连接请求
                    dataParserWorker = new PingWorker(requestBytes);
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + requestMsgId);
            }
        } else {
            //发送登录反馈
            log.info("客户端未登录或登录已超时:" + headerMap.get("MSG_GNSSCENTERID"));
            dataParserWorker = new SessionOverWorker(requestBytes);
        }
        return dataParserWorker.doParse();
    }

四、获取学习资料

子涵把整个协议实现过程用到的一些技术知识点进行了整理,欢迎大家一起学习~👉

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子涵先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值