C#基于SuperSocket实现部标JT808-2019数据接收服务器

前言

在某宝购入一只采用部标JT808-2019的4G带GPS定位的监控摄像头,发现与该设备交互需要实现JT808服务器端,从而定制自己后续业务。

一、开发语言选型

在网上经过对该协议的一些了解,查找了一些开源代码,主要为以下两种关于JT808较成熟的开源项目:

https://github.com/SmallChi/JT808

https://gitee.com/yezhihao/jt808-server

结合本身业务需要,目前选用了第一种C#语言控制台应用程序,使用.Net Framework框架进行JT808接收服务器的开发,有需要的小伙伴也可以用第二种Java语言进行开发。

开发工具采用了微软的Visual Studio 2022。

二、添加包引用

1、SuperSocket

采用了SuperSocket1.6版本简化Socket服务端的开发工作。

在Visual Studio中使用NuGet安装SuperSocket。

其中下面那个Engine包是用来构建服务端用。

2、JT808.Protocol

两种方式

1)使用NuGet搜索安装,作者是SmallChi的

2)下载SmallChi的项目源码,自己编译后将编译好的dll引入到自己项目中

我采用了第二种方式,一是可以了解具体实现,二是有部分类需要细微的修改下,后面会提到。

三、关键步骤实现

1、自定义请求(RequestInfo)

主要用来封装JT808数据包,便于下一步处理。

JT808RequestInfo.cs

public class JT808RequestInfo : IRequestInfo
{

    public JT808RequestInfo() { }

    public string Key{get;set;} = "jt808";

    public byte[] SourceBytes { get;set;}

    /// <summary>
    /// 起始符
    /// </summary>
    public const byte BeginFlag = 0x7e;
    /// <summary>
    /// 终止符
    /// </summary>
    public const byte EndFlag = 0x7e;

    public JT808RequestInfo(ushort msgId, JT808HeaderMessageBody messageBodyProperty, JT808Version version, string terminalPhoneNo, ushort msgNum, byte[] bodies, byte checkCode)
    {
        MsgId = msgId;
        MessageBodyProperty = messageBodyProperty;
        Version = version;
        TerminalPhoneNo = terminalPhoneNo;
        MsgNum = msgNum;
        Bodies = bodies;
        CheckCode = checkCode;
    }

    /// <summary>
    /// 起始符,1字节
    /// </summary>
    public byte Begin { get; set; } = BeginFlag;

    /// <summary>
    /// 消息ID,2字节
    /// </summary>
    public ushort MsgId { get; set; }

    /// <summary>
    /// 消息体属性
    /// </summary>
    public JT808HeaderMessageBody MessageBodyProperty { get; set; }

    /// <summary>
    /// 808版本号
    /// </summary>
    public JT808Version Version { get; set; }

    /// <summary>
    /// 终端手机号
    /// 根据安装后终端自身的手机号转换。手机号不足 12 位,则在前补充数字,大陆手机号补充数字 0,港澳台则根据其区号进行位数补充
    /// (2019版本)手机号不足 20 位,则在前补充数字 0
    /// </summary>
    public string TerminalPhoneNo { get; set; }

    /// <summary>
    /// 消息流水号
    /// 发送计数器
    /// 占用两个字节,为发送信息的序列号,用于接收方检测是否有信息的丢失,上级平台和下级平台接自己发送数据包的个数计数,互不影响。
    /// 程序开始运行时等于零,发送第一帧数据时开始计数,到最大数后自动归零
    /// </summary>
    public ushort MsgNum { get; set; }

    /// <summary>
    /// 消息总包数
    /// </summary>
    public ushort PackgeCount { get; set; }
    /// <summary>
    /// 报序号 从1开始
    /// </summary>
    public ushort PackageIndex { get; set; }

    /// <summary>
    /// 数据体
    /// </summary>
    public byte[] Bodies { get; set; }

    /// <summary>
    /// 校验码
    /// 从消息头开始,同后一字节异或,直到校验码前一个字节,占用一个字节。
    /// </summary>
    public byte CheckCode { get; set; }

    /// <summary>
    /// 终止符
    /// </summary>
    public byte End { get; set; } = EndFlag;
}

2、自定义AppSession

AppSession 代表一个和客户端的逻辑连接,基于连接的操作应该定于在该类之中。你可以用该类的实例发送数据到客户端,接收客户端发送的数据或者关闭连接。

SocketSession.cs

/// <summary>  
/// 自定义连接类SocketSession,继承AppSession,并传入到AppSession  
/// </summary>  
public class SocketSession : AppSession<SocketSession, JT808RequestInfo>
{
    public override void Send(string message)
    {
        Console.WriteLine("发送消息:" + message);
        base.Send(message);
    }


    protected override void OnSessionStarted()
    {
        //输出客户端IP地址  
        Console.WriteLine(this.LocalEndPoint.Address.ToString());
        //this.Send("Hello User,Welcome to SuperSocket Telnet Server!");
    }

    


    /// <summary>  
    /// 连接关闭  
    /// </summary>  
    /// <param name="reason"></param>  
    protected override void OnSessionClosed(CloseReason reason)
    {
        base.OnSessionClosed(reason);
    }

    //protected override void HandleUnknownRequest(JT808RequestInfo requestInfo)
    //{
    //    Console.WriteLine($"遇到未知的请求 Key:" + requestInfo.Key + $" Body:" + requestInfo.Body);
    //    base.HandleUnknownRequest(requestInfo);
    //}

    /// <summary>  
    /// 捕捉异常并输出  
    /// </summary>  
    /// <param name="e"></param>  
    protected override void HandleException(Exception e)
    {
        this.Send("error: {0}", e.Message);
    }



}

3、自定义AppServer

AppServer 代表了监听客户端连接,承载TCP连接的服务器实例。理想情况下,我们可以通过AppServer实例获取任何你想要的客户端连接,服务器级别的操作和逻辑应该定义在此类之中。

SocketServer.cs

public class SocketServer : AppServer<SocketSession, JT808RequestInfo>
{

    public SocketServer()
        : base(new MyReceiveFilterFactory())
    {

        //业务处理线程1
        Thread rcfsth = new Thread(xxxxx);
        rcfsth.IsBackground = true;
        rcfsth.Start();

        //业务处理线程2
        Thread locationTh = new Thread(xxxxxxxx);
        locationTh.IsBackground = true;
        locationTh.Start();


    }



    


    protected override bool Setup(IRootConfig rootConfig, IServerConfig config)
    {
        Console.WriteLine("正在准备配置文件");
        return base.Setup(rootConfig, config);
    }

    protected override void OnStarted()
    {
        Console.WriteLine("服务已开始");
        base.OnStarted();
    }

    protected override void OnStopped()
    {
        Console.WriteLine("服务已停止");
        base.OnStopped();
    }


    /// <summary>  
    /// 输出新连接信息  
    /// </summary>  
    /// <param name="session"></param>  
    protected override void OnNewSessionConnected(SocketSession session)
    {
        base.OnNewSessionConnected(session);
        //输出客户端IP地址  
        Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":连接");
    }


    /// <summary>  
    /// 输出断开连接信息  
    /// </summary>  
    /// <param name="session"></param>  
    /// <param name="reason"></param>  
    protected override void OnSessionClosed(SocketSession session, CloseReason reason)
    {
        base.OnSessionClosed(session, reason);
        Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":断开连接");
    }
}

4、自定义接收过滤器(ReceiveFilter)

由于JT808数据包每个数据包起始标记和结束标记都以7E作为特殊标记,所以这里采用BeginEndMarkReceiveFilter - 带起止符的协议,来接收客户端发来的数据包。

将该过滤器接收到的数据包根据JT808-2019协议内容去处理,并封装成JT808RequestInfo对象返回,这里我使用了Skip().Take()方式去取其中的字节数组,如果对效率有要求的小伙伴可以用Array.Copy()方式取。

MyReceiveFilter.cs

public class MyReceiveFilter : BeginEndMarkReceiveFilter<JT808RequestInfo>
{

    //开始和结束标记也可以是两个或两个以上的字节
    private readonly static byte[] BeginMark = new byte[] { 0x7e };
    private readonly static byte[] EndMark = new byte[] { 0x7e };


    //private readonly static byte[] decode7d01 = new byte[] { 0x7d, 0x01 };
    //private readonly static byte[] decode7d02 = new byte[] { 0x7d, 0x02 };

    public MyReceiveFilter() : base(BeginMark, EndMark)
    {
    }

    protected override JT808RequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
    {
        //解析808协议

        byte[] sourceBytes = readBuffer;

        //对数据包中的固定两字节数据进行反转义


        //先对0x7d,0x02替换为0x7e
        //再对0x7d,0x01替换为0x7d
        string readBufferStr = ByteUtils.ToHexStrFromByte(readBuffer).Replace("7D 02", "7E").Replace("7D 01", "7D");
        readBuffer = ByteUtils.ToHexBytes(readBufferStr.Replace(" ", ""));

        

        //第一个字节 0x7e 起始位
        //2-3字节 消息ID
        int msgId = (int)readBuffer[offset + 1] * 256 + (int)readBuffer[offset + 2];
        //4-5字节 消息体属性
        JT808HeaderMessageBody messageBodyProperty = new JT808HeaderMessageBody();

        //得到4-5字节数组
        byte[] headerBuffer = new byte[] { readBuffer[offset + 3], readBuffer[offset + 4] };
        //字节数组转二进制字符串
        string binaryString = ByteUtils.ConvertByteArrayToBinaryString(headerBuffer);

        //获取消息体长度 bit0-bit9
        messageBodyProperty.DataLength = Convert.ToInt32(binaryString.Substring(binaryString.Length - 10), 2);

        //获取数据加密 bit10~bit10为1则是RSA加密
        if (binaryString[5] == '1')
            messageBodyProperty.Encrypt = JT808.Protocol.Enums.JT808EncryptMethod.RSA;
        else
            messageBodyProperty.Encrypt = JT808.Protocol.Enums.JT808EncryptMethod.None;

        //获取是否分包 bit13
        if (binaryString[2] == '1')
            messageBodyProperty.IsPackage = true;
        else
            messageBodyProperty.IsPackage = false;

        //获取版本号


        //获取保留号 bit15
        messageBodyProperty.Reserve = int.Parse(binaryString[0].ToString());

        JT808Version jT808Version = JT808Version.JTT2019;
        //协议版本号 第6字节,目前只支持2019
        //if (readBuffer[5] == 0x01)

        

        //获取手机号,第 7-16字节
        byte[] phoneBuffer = readBuffer.Skip(6).Take(10).ToArray();
        //字节数组转BCD字符串
        string terminalPhoneNo = ByteUtils.ByteArrayToBCDString(phoneBuffer).TrimStart('0');

        //获取消息体流水号,第17-18字节
        int msgNum = (int)readBuffer[offset + 16] * 256 + (int)readBuffer[offset + 17];

        int beginPackage = 18;
        ushort packgeCount = 0;
        ushort packageIndex = 0;
        //如果是分包就有消息包封装项
        if (messageBodyProperty.IsPackage)
        {
            //消息包总数,第19-20字节
            packgeCount = BitConverter.ToUInt16(readBuffer.Skip(18).Take(2).Reverse().ToArray(), 0);

            //包序号,第21-22字节
            packageIndex = BitConverter.ToUInt16(readBuffer.Skip(20).Take(2).Reverse().ToArray(), 0);

            beginPackage = 22;
        }

        //消息体,第19+分包字节至消息体长度
        byte[] bodiesBuffer = readBuffer.Skip(beginPackage).Take(messageBodyProperty.DataLength).ToArray();

        //真实校验码
        byte realCheckCode = ByteUtils.GetXorCode(readBuffer.Skip(1).Take(readBuffer.Length - 3).ToArray());

        //校验码
        byte checkCode = readBuffer[readBuffer.Length - 2];

        if (realCheckCode != checkCode)
            Console.WriteLine("本次校验失败!真实检验码:" + realCheckCode + ",实际校验码:" + checkCode);

        JT808RequestInfo jT808RequestInfo = new JT808RequestInfo((ushort)msgId, messageBodyProperty,jT808Version, terminalPhoneNo,(ushort)msgNum,bodiesBuffer,checkCode);
        jT808RequestInfo.PackgeCount = packgeCount;
        jT808RequestInfo.PackageIndex = packageIndex;
        jT808RequestInfo.SourceBytes = sourceBytes;


        return jT808RequestInfo;

    }
}

注意:如果使用SmallChi的库,可以将其中的方法改成以下代码,然后将JT808RequestInfo改成JT808Package,别的类里也改一下。使用这种方式我没有测试过会不会实际运行出问题,有时间的小伙伴可以自己测以下。这里面的DefaultGlobalConfig这个类,如果使用NuGet安装的包可能会引入不了,因为他是一个内部类,需要修改源代码,修改为public再生成引用到自己项目即可。

//解析808协议

byte[] sourceBytes = readBuffer;


//============================采用 SmallChi 的解包方法
JT808Package jT808Package = new JT808Package();
var reader = new JT808MessagePackReader(sourceBytes, JT808Version.JTT2019);
reader.Decode();
IJT808Config jT808Config = new DefaultGlobalConfig();
jT808Package = jT808Package.Deserialize(ref reader, jT808Config);

5、实现接收过滤器工厂(ReceiveFilterFactory)

MyReceiveFilterFactory.cs

public class MyReceiveFilterFactory : IReceiveFilterFactory<JT808RequestInfo>
{
    public IReceiveFilter<JT808RequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint)
    {
        return new MyReceiveFilter();
    }
}

6、自定义Command处理封装好的数据包内容

命令行协议是一种被广泛应用的协议。一些成熟的协议如 Telnet, SMTP, POP3 和 FTP 都是基于命令行协议的。 在SuperSocket 中, 如果你没有定义自己的协议,SuperSocket 将会使用命令行协议, 这会使这样的协议的开发变得很简单。

JT808PackCommand.cs

public class JT808PackCommand : CommandBase<SocketSession, JT808RequestInfo>
{


   

    /// <summary>
    /// 平台通用应答
    /// </summary>
    /// <param name="session"></param>
    /// <param name="requestInfo"></param>
    /// <param name="ackMsgId"></param>
    public void PlatformCommonReply(SocketSession session, JT808RequestInfo requestInfo,ushort ackMsgId)
    {
        JT808Package jT808Package = new JT808Package();
        jT808Package.Header = new JT808Header
        {
            MsgId = (ushort)JT808MsgId._0x8001,
            ManualMsgNum = 0,
            TerminalPhoneNo = requestInfo.TerminalPhoneNo
        };
        JT808_0x8001 jT808_8001 = new JT808_0x8001();
        jT808_8001.MsgNum = requestInfo.MsgNum;
        jT808_8001.AckMsgId = ackMsgId; 
        jT808_8001.JT808PlatformResult = JT808PlatformResult.succeed;
        jT808Package.Bodies = jT808_8001;
        JT808Serializer jT808Serializer = new JT808Serializer();
        byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019);
        session.Send(data, 0, data.Length);
        //Console.WriteLine(jT808_8001.Description);
    }

    public override string Name
    {
        get { return "jt808"; }
    }

    public override void ExecuteCommand(SocketSession session, JT808RequestInfo requestInfo)
    {
        ushort msgId = requestInfo.MsgId;
        //Console.WriteLine("收到一条jt808消息,消息ID:" + msgId);

       

        try
        {
            switch (msgId)
            {
                //终端通用应答
                case 0x0001:
                    {

                        Custom_JT808_0x0001 jT808_0X0001 = Custom_JT808_0x0001.Deserialize(requestInfo.Bodies);
                        //Console.WriteLine(jT808_0X0001.Description);


                        PlatformCommonReply(session, requestInfo, jT808_0X0001.ReplyMsgId);
                    }
                    break;
                //查询服务器时间
                case 0x0004:
                    {
                        //Console.WriteLine("查询服务器时间");

                        JT808Package jT808Package = new JT808Package();
                        jT808Package.Header = new JT808Header
                        {
                            MsgId = (ushort)JT808MsgId._0x8004,
                            ManualMsgNum = 0,
                            TerminalPhoneNo = requestInfo.TerminalPhoneNo
                        };

                        JT808_0x8004 jT808_8004 = new JT808_0x8004();
                        jT808_8004.Time = DateTime.UtcNow;
                        jT808Package.Bodies = jT808_8004;
                        JT808Serializer jT808Serializer = new JT808Serializer();
                        byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019);

                        session.Send(data, 0, data.Length);

                    }
                    break;
                //终端注册
                case 0x0100:
                    {
                        Custom_JT808_0x0100 jT808_0X0100 = Custom_JT808_0x0100.Deserialize(requestInfo.Bodies);


                        JT808Package jT808Package = new JT808Package();
                        jT808Package.Header = new JT808Header
                        {
                            MsgId = (ushort)JT808MsgId._0x8100,
                            ManualMsgNum = 0,
                            TerminalPhoneNo = requestInfo.TerminalPhoneNo
                        };
                        JT808_0x8100 jT808_8100 = new JT808_0x8100();
                        jT808_8100.AckMsgNum = requestInfo.MsgNum;
                        jT808_8100.JT808TerminalRegisterResult = JT808TerminalRegisterResult.success;
                        jT808_8100.Code = jT808_0X0100.TerminalId + "," + jT808_0X0100.PlateNo;
                        jT808Package.Bodies = jT808_8100;

                        JT808Serializer jT808Serializer = new JT808Serializer();
                        byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019);

                        session.Send(data, 0, data.Length);

                    }

                    break;
                //终端鉴权
                case 0x0102:
                    {
                        Custom_JT808_0x0102 jT808_0102 = Custom_JT808_0x0102.Deserialize(requestInfo.Bodies);

                        //鉴权成功后保存JT808终端客户端信息
                        JT808ClientCache.AddJT808ClientCache(requestInfo.TerminalPhoneNo, session.SessionID);

                        PlatformCommonReply(session, requestInfo, requestInfo.MsgId);
                    }
                    break;

                //位置信息汇报
                case 0x0200:
                    {
                        Custom_JT808_0x0200 jT808_0X0200 = Custom_JT808_0x0200.Deserialize(requestInfo.Bodies);
                        Console.WriteLine(jT808_0X0200.Description);

                        PlatformCommonReply(session, requestInfo, requestInfo.MsgId);
                    }
                    break;
                //定位数据批量上传
                case 0x0704:
                    {
                        PlatformCommonReply(session, requestInfo, requestInfo.MsgId);
                    }
                    break;
                //多媒体数据上传
                case 0x0801:
                    {
                        //如果有分包内容
                        if (requestInfo.PackgeCount > 0)
                        {
                            //Console.WriteLine(requestInfo.PackageIndex);
                            string phone = requestInfo.TerminalPhoneNo;
                            //第一个多媒体数据包
                            if (requestInfo.PackageIndex == 1)
                            {
                                Custom_JT808_0x0801 jT808_0X0801 = Custom_JT808_0x0801.Deserialize(requestInfo.Bodies);

                                //添加多媒体数据包缓存
                                MultimediaCache.AddMultimediaCache(phone, jT808_0X0801);

                                PlatformCommonReply(session, requestInfo, requestInfo.MsgId);
                            }
                            //中间的数据包
                            if (requestInfo.PackageIndex > 1 && requestInfo.PackageIndex < requestInfo.PackgeCount)
                            {
                                //追加多媒体数据包缓存
                                MultimediaCache.AppendMultimediaByte(phone, requestInfo.Bodies);
                                PlatformCommonReply(session, requestInfo, requestInfo.MsgId);
                            }
                            //最后一个包,保存数据,并回应
                            if (requestInfo.PackageIndex == requestInfo.PackgeCount)
                            {
                                //追加多媒体数据包缓存
                                MultimediaCache.AppendMultimediaByte(phone, requestInfo.Bodies);
                                //保存到本地
                                Custom_JT808_0x0801 jT808_0X0801 = MultimediaCache.GetMultimediaCache(phone);
                                byte[] multimediaDataPackage = jT808_0X0801.MultimediaDataPackage;

                                //Console.WriteLine(ByteUtils.ToHexStrFromByte(multimediaDataPackage).Replace(" ",""));

                                uint mediaId = jT808_0X0801.MultimediaId;
                                //ByteUtils.BytesToFile(multimediaDataPackage, "sample.jpg");
                                File.WriteAllBytes("Img\\" + requestInfo.MsgNum + "_saved_image.jpg", multimediaDataPackage);

                                //移除多媒体数据包缓存
                                MultimediaCache.RemoveMultimediaCache(phone);
                                //应答
                                JT808Package jT808Package = new JT808Package();
                                jT808Package.Header = new JT808Header
                                {
                                    MsgId = (ushort)JT808MsgId._0x8800,
                                    ManualMsgNum = 0,
                                    TerminalPhoneNo = requestInfo.TerminalPhoneNo
                                };
                                JT808_0x8800 jT808_0X8800 = new JT808_0x8800();
                                jT808_0X8800.MultimediaId = mediaId;
                                jT808_0X8800.RetransmitPackageCount = 0;
                                jT808_0X8800.RetransmitPackageIds = new byte[0];//一定要定义一个空数组
                                jT808Package.Bodies = jT808_0X8800;

                                JT808Serializer jT808Serializer = new JT808Serializer();
                                byte[] data = jT808Serializer.Serialize(jT808Package, JT808Version.JTT2019);

                                session.Send(data, 0, data.Length);

                            }
                        }
                    }
                    break;
                default:
                    {
                        PrintMessage.PrintLn("未知命令" + msgId.ToString("x8"),ConsoleColor.Yellow);
                        PlatformCommonReply(session, requestInfo, requestInfo.MsgId);
                    }

                    break;

            }
        }catch (Exception ex)
        {
            LogHelper.WriteError2(ex, "ExecuteCommand error");
        }
        

    }
}

其中Custom_xxx是去解析JT808数据包中的包内容并封装的,解析方式参考过滤器那里,可以自己根据808协议去写。如果用了SmallChi的方式在过滤器中,可以直接使用JT808Package去处理业务代码,无需定义自己的Custom_xxx去解析包内容。

7、配置App.config使用BootStrap启动SuperSocket

App.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<configSections>
		<!--log 日志记录-->
		<section name="log4net" type="System.Configuration.IgnoreSectionHandler" />
		<!--SocketEngine-->
		<section name="superSocket" type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine" />
	</configSections>
	<!--SuperSocket服务配置信息 serverType是项目的服务如我自定义的Socketserver-->
	<!--name: 实例名称
      serverType: 实例运行的AppServer类型
      ip: 侦听ip
      port: 侦听端口-->
	<superSocket>
		<servers>
			<!--textEncoding 编码方式"gb2312","utf-8" 默认是acii maxRequestLength 默认1024 这里必须改大点,不然无故踢掉客户端-->
			<server name="Sft808Server" textEncoding="gb2312" serverType="Sft808Server.Socket.SocketServer,Sft808Server" ip="Any" port="20177" maxConnectionNumber="100" maxRequestLength="1073741824">
			</server>
		</servers>
	</superSocket>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
</configuration>

四、最后

除此之外,还要用到一个JT1078流媒体服务器去接收摄像头传过来的视频流,我是用了Java去接收该视频流。

后来项目暂停了,所以就没再继续深入下去了。

  • 25
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
部标JT809-2011是交通部于2011年发布的《车载卫星定位系统应用层标准》的第一版。该标准是指导车载卫星定位系统的应用和开发的规范文件。然而,在2011年之后,为了进一步完善和补充该标准,交通部发布了JT809-2019版本。与JT809-2011相比,JT809-2019在多个方面进行了修订和补充。 其中主要的区别包括对应急事件响应机制的完善和对数据报文格式的优化。在JT809-2019中,新增了与应急事件响应相关的规定,包括应急事件的分类、报告、处置等方面的规定。此外,针对紧急情况下的语音报警和视频传输等应急措施也进行了规范和完善。而在数据报文格式方面,JT809-2019进行了优化,提高了数据传输的效率和可靠性。此外,还新增加了一些新的报文类型和内容,如车辆状态信息实时上传、实时视频数据传输等。 总的来说,JT809-2011是交通部发布的第一版车载卫星定位系统应用层标准,而JT809-2019是在此基础上进行了修订和补充的最新版本。通过对应急事件响应机制和数据报文格式的优化,JT809-2019进一步提升了车载卫星定位系统的应用效果和性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [部标JT809-2011与JT809-2019有哪些区别?](https://blog.csdn.net/lingx_gps/article/details/129954257)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值