车载通信协议-列车实时数据协议(TRDP)

TCNOPEN是一个铁路行业相关的合作伙伴创建的开源的倡议,其目的是建立一些新的或即将出台的铁路标准的关键部分,通常以TCN命名。

TCN(列车通信网络)是IEC(国际电工委员会)第43工作组制定的一系列国际标准(IEC61375),规定了列车内和车辆间数据通信的通信系统。它目前正在世界上成千上万的列车上使用,以便允许电子设备在同一列车上运行时交换信息。

TCNOPEN遵循开源方案,因为软件是由参与公司共同开发的,根据它们的角色,从而实现更便宜、更快和更好的质量结果。

目标

TCNOPEN的总体目标是提供一个合适的环境,开放的兴趣团体。其中合作伙伴公司可以合作开发符合TCN标准的新组件。

TCN是宏观的称呼,实际上标准定义了TCN分为列车骨干网和编组网两个层次,WTB或者ETB是列车骨干网层次,MVB、CAN、ECN是编组网层次。

对于每个需求,将启动一个特定的开源项目,该项目将贯穿所有需要的阶段:规范、开发、测试、支持。第一个项目目前正在进行中,与开发的TRDP模块有关。

TRDP(列车实时数据协议)是TCP或UDP协议与使用网络的应用之间的中间模块。

它包括一个可选附加的安全层(SDT)。SDT是在不可信通信信道上的端到端协议。

SDT实现IEC62280(En50159),并支持安全数据源与一个或多个安全数据接收器之间的安全相关数据的传输。

TRDP项目介绍

国际标准化组织(IEC)推出了基于以太网的列车通信网络标准,包括列车骨干以太网(Ethernet Train Backbone,ETB,由IEC61375-2-5定义)和编组以太网(Ethernet Consist Network,ECN,由IEC61375-3-4定义)。ETB界定了不同列车编组之间的互连接和互操作规范,而ECN界定了在每个编组内各个终端设备构成的以太网的通信规范。在2015年7月出台了机车车辆使用的工业以太网标准IEC61375-2-3,Edition1.0,其为ECN与ETB的4~7层都将使用专为铁路用途开发的自主协议列车实时数据协议(Train Real-time Data Protocol,TRDP),并通过行业团体TCNOpen以开源的形式公开,可在对应网站https://sourceforge.net/projects/tcnopen/下载其源码 ,也可以通过svn:https://svn.code.sf.net/p/tcnopen/trdp/ 可以下载最新的版本。

TRDP协议栈在以太网中所处位置如图1所示。

TRDP协议栈在应用系统中位置如图2所示。

编译:

windows下可以使用VS工具直接编译相应代码 ;

linux编译输入一下命令:

cd trdp/config
chmod a+x *
mv buildsettings_posix_TEMPLATE_ buildsetings_%TARGET%
cd ..
source config/buildsettings_%TARGET%
sudo make LINUX_config
sudo make //或者make all //make help查看编译选项
//生成目标文件放在bld/output下

测试用例:(trdp核心代码是为了生成trdp.a库文件,具体使用都需要开发者进行研究)

Example目录下有sendHello和receiveHello的测试代码,VS下也有测试其他功能的代码。

后记:基本介绍就到这里了,了解标准可以看IEC61375-2-3。

TRDP通信协议结构:

以下是技术文档中的:几个重要信息的解释。

Process Data (PD) is data that is cyclically distributed among many applications. Payload size is limited to 1436 bytes (without SDT).

Message Data (MD) is data that is sent event driven from one application to one or more other applications. Payload using UDP can be up to ~64 Kbytes, using TCP up to ~4 GBytes.

The TRDPLight contains the base functionality for PD, UDP-MD and TCP-MD.

The TRDPXML contains the functionality for reading TRDP XML configuration files.

The TRDPSpy contains the wireshark plugin for interpreting TRDP telegrams.

--TRDPSpy 包含解读TRDP报文的wireshark插件。

对于PIS与TMS系统通信来说,只需要了解PD(过程数据)通信结构即可。

在当前铁路的一些车辆中,MVB通信逐渐被淘汰,实时以太网TRDP的应用逐渐的在增多,而我们目前的所用的抓包器在TRDP网络中抓到的数据包显示为UDP,虽然可以看到数据包的内容,但加入这个链接库后,在wireshark就可以看到名为TRDP的数据包,且也可以使用trdp的命令进行抓取特定comid的数据包。本人所使用的Wireshark版本为Wireshark-win64-2.2.7.exe。将trdp_spy.dll放置与wireshark的安装目录下的plugins目录下当前版本号文件夹目录下(如:D:\Program Files\Wireshark\plugins\2.2.4),重启后即可。

TRDP协议介绍

TRDP(Train Real-time Data Protocol)协议是纯正的轨道交通领域的概念。

其也是应用是处于编组网层次,但是该协议并不是处于数据链路层,个人理解其处于应用层。其是基于UDP、TCP的通信协议,在使用过程中终端设备在标准以太网的基础上新增TRDP协议栈即可。

TRDP的软件是完全开源的,具体可以登入https://www.tcnopen.eu/进行查看。

TRDP是一个软实时的实时以太网,其目前已经在地铁车辆中逐步推广使用,替代原有的MVB网络,主要解决MVB网络通信的数据量太小的问题。

TRDP协议中传输的PD(Process-Data)和MD(Message-Data)。

PD主要用于列车控制,传输命令和状态信息,数据量大,要求高可靠性、实时性和确定性,一般为周期性传送。

MD主要用于故障和诊断信息,数据量长短不一,一般都是按需传送,需要确保实时性。

PD和MD通信方式都是基于了生产者/消费者模型,包含了PUSH和PULL操作,设备角色有publisher、subscriber和requester。

PD消息PUSH操作一般是publisher周期性发布消息,subscriber接收此PD消息;PULL操作是subscriber或者requester请求消息,publisher发布需求的COMID消息,在这里requester可以是subscriber。

MD消息通常请求者发出请求,已经监听的应答者做出回复,请求者可以再确认收到应答。

/***************************************************************************************************/

我下载的是1.4.0.0的发布版,通过目录可以基本先了解大致结构。

源码位于src目录下,/api提供了基本对外的API接口,/common包含了协议处理的核心代码,/vos包含了与操作系统对接的代码,支持了linux、VxWorks、Windows,操作系统接口大致为socket网络通信处理、内存管理以及任务调度。

/test 、/example 目录下主要是测试代码,

/spy目录下是wireshark的抓包工具的插件,可以针对trdp报文进行解析,

/bld是存放编译代码生成的目标文件,linux下可以在/linux-rel目录里执行测试的.exe文件。

TRDP协议代码的函数也是具有较高的辨识度,tlc表示处理通用接口,tlm表示处理MD数据接口,tlp表示处理PD数据接口。

分析/example目录示例代码,trdp通用流程大致为:

start->tlc_init->tlc_openSession->tlp_subscribe/tlp_publish->while(tlc_process)->end.

TRDP还有很多的扩展功能,因此协议可以根据业务的需求,编写适合不同场景的代码。

现在总结一下PD通信方式。

PD一般是周期性传输数据,协议规定通信方式为UDP。之前学习基于socket通信时,也只是知道UDP和TCP相比,UDP是面向无连接的,具体还怎么使用过,这次PD的收发包模型可以好好学习一波了!!!

UDP,用户数据包协议,属于传输层协议。传输数据时,不与对面先连接,而是直接把数据传送给对方。因此,UDP适用于传送数据量少,对可靠性要求不高的场景。例如比较常见的SNMP协议,TFTP协议,DNS协议等。如果SNMP协议每次请求MIB数据都需要先三次握手的话,那效率也太低了。

UDP协议通信流程非常简单。Client一般只要socket()一个socket,从而使用sendto()就可以向目的地址发送数据报了。Server一般socket()一个socket,对这个socket进行bind(),绑定端口号和IP,使用recvfrom就可以收到发送给自己的数据报了,如果需要回应,使用sendto()就可以发送了。当然,最后我们都需要close()申请的socket。

#include <sys/types.h>          
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain参数用于设置网络通信的域,AF_INET,IPV4,   AF_INET6, IPV6,其余的还有很多,一般只需牢记这两个。
type参数用于指定数据类型,
SOCK_STREAM         Provides sequenced, reliable, two-way, connection-based byte streams.   //用于TCP
SOCK_DGRAM          Supports datagrams (connectionless, unreliable messages ). //用于UDP
SOCK_RAW            Provides raw network protocol access.  //RAW类型,用于提供原始网络访问
protocol参数指定协议类型,默认可以置0,UDP是17.
返回值成功时返回socket文件描述符,失败则返回-1
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:正在监听端口的套接口文件描述符,通过socket获得
数buf:发送缓冲区,往往是使用者定义的数组,该数组装有要发送的数据
len:发送缓冲区的大小,单位是字节
flags:填0即可
dest_addr:指向接收数据的主机地址信息的结构体,也就是该参数指定数据要发送到哪个主机哪个进程
addrlen:表示第五个参数所指向内容的长度
返回值:成功:返回发送成功的数据长度
       失败: -1
这里要注意的是,在准备数据时,一般不会直接使用struct sockaddr类型,而是用srtuct sockaddr_in ,在sendto时强制转换类型。这里是因为,底层会再次类型转换回去。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:正在监听端口的套接口文件描述符,通过socket获得
buf:接收缓冲区,往往是使用者定义的数组,该数组装有接收到的数据
len:接收缓冲区的大小,单位是字节
flags:填0即可
src_addr:指向发送数据的主机地址信息的结构体,也就是我们可以从该参数获取到数据是谁发出的
addrlen:表示第五个参数所指向内容的长度
返回值:成功:返回接收成功的数据长度
       失败: -1
 
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
sockfd:正在监听端口的套接口文件描述符,通过socket获得
my_addr:需要绑定的IP和端口
addrlen:my_addr的结构体的大小
返回值:成功:0
       失败:-1

#include <unistd.h>

int close(int fd);

高级UDP编程【参考了大佬https://www.cnblogs.com/skyfsm/p/6287787.html

其实,在UDP中,也有connect()函数,但是仅仅只是为了确定对方的地址,没有其他的含义。

因为UDP自身的特点,决定了UDP会相对于TCP存在一些难以解决的问题。第一个就是UDP报文缺失问题。

在UDP服务器客户端的例子中,如果客户端发送的数据丢失,服务器会一直等待,直到客户端的合法数据过来。如果服务器的响应在中间被路由丢弃,则客户端会一直阻塞,直到服务器数据过来。

防止这样的永久阻塞的一般方法是给客户的recvfrom调用设置一个超时,大概有这么两种方法:

1)使用信号SIGALRM为recvfrom设置超时。首先我们为SIGALARM建立一个信号处理函数,并在每次调用前通过alarm设置一个5秒的超时。如果recvfrom被我们的信号处理函数中断了,那就超时重发信息;若正常读到数据了,就关闭报警时钟并继续进行下去。

使用select为recvfrom设置超时

设置select函数的第五个参数即可。

UDP发送报文可能会产生乱序问题,乱序就是发送数据的顺序和接收数据的顺序不一致,例如发送数据的顺序为A、B、C,但是接收到的数据顺序却为:A、C、B。产生这个问题的原因在于,每个数据报走的路由并不一样,有的路由顺畅,有的却拥塞,这导致每个数据报到达目的地的顺序就不一样了。UDP协议并不保证数据报的按序接收。

解决这个问题的方法就是发送端在发送数据时加入数据报序号,这样接收端接收到报文后可以先检查数据报的序号,并将它们按序排队,形成有序的数据报。

UDP流量控制问题

总所周知,TCP有滑动窗口进行流量控制和拥塞控制,反观UDP因为其特点无法做到。UDP接收数据时直接将数据放进缓冲区内,如果用户没有及时将缓冲区的内容复制出来放好的话,后面的到来的数据会接着往缓冲区放,当缓冲区满时,后来的到的数据就会覆盖先来的数据而造成数据丢失(因为内核使用的UDP缓冲区是环形缓冲区)。因此,一旦发送方在某个时间点爆发性发送消息,接收方将因为来不及接收而发生信息丢失。

解决方法一般采用增大UDP缓冲区,使得接收方的接收能力大于发送方的发送能力。

int n = 220 * 1024; //220kB

setsocketopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

这样我们就把接收方的接收队列扩大了,从而尽量避免丢失数据的发生。

经过一段时间的学习,基于以太网交换机的TRDP PD开发调试结果已经正常了,故此,记录一下。

TRDP基于生产者、消费者模型。

TRDP PD 通信模式有两种,push和pull。在这两种模式中,网络设备又可以分为三种角色:publisher、subscriber、requester.

publisher是数据的提供者,在push和pull模式中负责发送注册的comId的数据,即所谓的生产者;

subscriber是数据的接受者,这push和pull模式中负责接收注册的comid的数据,即所谓的消费者;

requester是数据的请求者,在pull模式中负责对publisher发起请求,pd-pdu的头部中会带有相应的reply IP和reply comID,如果头部中没有指定需要reply的IP和comID,则publisher会把接收到的报文的源作为reply的目的给发送回去。

在pull模式中,requester也可以是subscriber。

/************************************************************************************************/

tlc_ 提供了trdp的通用接口,因此在验证功能时非常好用。

//trdp 协议栈初始化

//初始化打印函数和VOS的内存管理

EXT_DECL TRDP_ERR_T tlc_init (
    const TRDP_PRINT_DBG_T  pPrintDebugString,
    void                    *pRefCon,
    const TRDP_MEM_CONFIG_T *pMemConfig);

//打开一个trdp 协议栈session入口,这里主要是进行协议栈的配置,返回pAppHandle,这个至关重要

EXT_DECL TRDP_ERR_T tlc_openSession (
    TRDP_APP_SESSION_T              *pAppHandle,
    TRDP_IP_ADDR_T                  ownIpAddr,
    TRDP_IP_ADDR_T                  leaderIpAddr,
    const TRDP_MARSHALL_CONFIG_T    *pMarshall,
    const TRDP_PD_CONFIG_T          *pPdDefault,
    const TRDP_MD_CONFIG_T          *pMdDefault,
    const TRDP_PROCESS_CONFIG_T     *pProcessConfig);

1、PD publisher验证

数据的发布者需要先准备发送的数据,将comID、destip、timeout等信息作为一个实体添加到协议栈的发送队列中

EXT_DECL TRDP_ERR_T tlp_publish (
    TRDP_APP_SESSION_T      appHandle,
    TRDP_PUB_T              *pPubHandle,
    const void              *pUserRef,
    TRDP_PD_CALLBACK_T      pfCbFunction,
    UINT32                  comId,
    UINT32                  etbTopoCnt,
    UINT32                  opTrnTopoCnt,
    TRDP_IP_ADDR_T          srcIpAddr,
    TRDP_IP_ADDR_T          destIpAddr,
    UINT32                  interval,
    UINT32                  redId,
    TRDP_FLAGS_T            pktFlags,
    const TRDP_SEND_PARAM_T *pSendParam,
    const UINT8             *pData,
    UINT32                  dataSize);
 
TRDP_ERR_T tlp_put (
    TRDP_APP_SESSION_T  appHandle,
    TRDP_PUB_T          pubHandle,
    const UINT8         *pData,
    UINT32              dataSize);

这里的每一个实体都是不重复的。因此,在周期推送数据的过程中,trdp 会 依次遍历自己的发送队列,将信息发送给目的地址。

这里的tlp_put()的功能就是将所需要发送的信息作为pd-pdu的dataset组织好数据报,放入发送队列中相应的节点存储。利用linux的select()函数来进行非阻塞的等待,周期性推送timeout的数据,即马上要发送的数据。

2、subscriber验证

subscriber是数据的接受者,需要先注册接收的信息,包含了源IP范围、comID,然后调用tlp_subscribe来注册到协议栈session的接收队列中。

EXT_DECL TRDP_ERR_T tlp_subscribe (
    TRDP_APP_SESSION_T  appHandle,
    TRDP_SUB_T          *pSubHandle,
    const void          *pUserRef,
    TRDP_PD_CALLBACK_T  pfCbFunction,
    UINT32              comId,
    UINT32              etbTopoCnt,
    UINT32              opTrnTopoCnt,
    TRDP_IP_ADDR_T      srcIpAddr1,
    TRDP_IP_ADDR_T      srcIpAddr2,
    TRDP_IP_ADDR_T      destIpAddr,
    TRDP_FLAGS_T        pktFlags,
    UINT32              timeout,
    TRDP_TO_BEHAVIOR_T  toBehavior)

协议栈根据timeout时间来接收绑定的socket数据,对接收到的数据进行校验,例如CRC、TOPOCOUNT、SRCIP、COMID。根据pdu的msgType来判断是否是Pr类型的数据,pr类型也就是请求包,收到这样的包需要查看发送队列是否存在comid相同的节点,如果存在,需要马上将数据作为msgType==Pp推送给reply IP,并且通知trdp user层(打印函数)。

3、requester验证

请求数据和publisher很相似,不过这里是调用了tlp_request来进行msgType==Pr数据的准备。

EXT_DECL TRDP_ERR_T tlp_request (
    TRDP_APP_SESSION_T      appHandle,
    TRDP_SUB_T              subHandle,
    UINT32                  comId,
    UINT32                  etbTopoCnt,
    UINT32                  opTrnTopoCnt,
    TRDP_IP_ADDR_T          srcIpAddr,
    TRDP_IP_ADDR_T          destIpAddr,
    UINT32                  redId,
    TRDP_FLAGS_T            pktFlags,
    const TRDP_SEND_PARAM_T *pSendParam,
    const UINT8             *pData,
    UINT32                  dataSize,
    UINT32                  replyComId,
    TRDP_IP_ADDR_T          replyIpAddr)

tlp_request会生成一个新的实体,放入trdp session的发送队列中去。如果收到相应的Pp报文,则需要通知trdp user层。

/******************************************************************************************/

PD数据的推拉模式,源码中已经写的比较独立和清晰,需要关注的点是和操作系统的对接,主要也就是socket编程。PD使用的是UDP协议,IANA 分配的port为17224,iec61375-2-3中规定的是20548。在UDP对接中,不同的系统的难度是不一样的,源码可以再Windows系统和linux系统直接生成相应的执行文件,适配网络平台则需要慢慢调试,验证相应的收发功能。

PDCom处理过程数据,而MDCom处理TCN上的消息数据通信。 TRDP与网络的其他用户共存,例如流通信(如TCP/IP)和基于尽力而为的通信(如UDP/IP)。 TRDP由两个级别组成:轻量TRDP...或外部,组建列车网络。 抽象层VOS(virtual OS)为操作系统(OS)和底层硬件之间提供了标准接口,这些接口由内部的TRDP功能以及上层应用所使用。该接口确保了TRDP可以适应不同的操作系统

TRDP运行

tlc_init()              TRDP协议栈初始化(初始化内存和参数)
tlc_opensession()    
tlp_subscribe()         订阅想要的COMID 的PD数据包
tlp_pulish()            发布要发送的COMID的PD数据包
tlc_process()           协议栈主循环,必须周期性调试用,所有的”实际接收发送数据包”都在此函数中实现
tlp_get()               接收数据包。其实是从缓冲区中拷贝出来。然后根据comID与status来处理数据包
tlp_put()               修改正在发送的过程数据(PD)数据包

轮询方式: (这种方式主循环采用no_block方式。)

协议栈初始化时注册回调函数,当想要的comID来到时,系统会调用注册的回调函数,回调函数应该尽可能的简单,推荐在回调函数中把数据拷贝到应用缓冲区,然后在主循环中处理数据。这种方式主循环采用block方式。

回调函数方式:

  • 11
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值