channel access是epics用来进行消息通信的专属协议,是典型的客户端/服务端模式。由于epics是处理分布式控制系统的平台,其物理拓扑结构就像标志性logo表示的一样,clients和servers分散在不同位置,他们之间的数据交换通过消息总线传递。
基本结构
- server
当启动ioc的时候,也就启动了服务端,epics连接设备,进行数据交互,更新数据库,逻辑运算。 - client
通常的客户端为各种监视程序,比如alarmer,archiver,或者基于ca库的模拟程序等。 - repeater
中继器,用来转播udp广播的,确保host上的多个client、server能够接收到数据包。
网络协议
ca协议是基于tcp/ip协议栈的,因此也遵循基本的网络约定。使用tcp进行数据交换,使用udp进行广播。
端口占用
在ca协议中,最重要的是serverPort和repeaterPort,前者用于监听client的名字搜索,并且发送心跳给repeater;后者转发搜索、心跳等广播数据报文。
EPICS_CA_SERVER_PORT = 5064
EPICS_CA_REPEATER_PORT = 5065
此外,server端还占有一个用于建立虚拟回路的端口,该端口号不确定。
物理信道
ca秉承资源最小化的原则,提出了虚拟回路virtual circuit的概念。虚拟回路是数据交换的真实物理通道,单个client与单个server之间只存在一个tcp连接,并不以pv数量的多少增长。这样设计的考虑点,在于不会过度浪费socket的资源,实现资源的复用性。
数据包
在分布式控制系统中,单个信号的数据量是相当小的,单次通信的流量主要消耗在报文头。针对这种小数据包的场景,ca协议引入了缓存buffer。对于每个虚拟回路都建立一个发送缓存队列,将所有的ca数据包都暂存起来,直到达到预定容量、预定超时、或者用户强制发送,则将数据打包,通过socket信道一次性发送。
数据包头
数据以典型的tcp字节流形式组织,数据包头有固定格式,固定长度为16字节。除了指令ID和内容长度,其他数据块的内容可能根据指令ID不同而不同,但不违背数据块长度约束。具体指令可以参考ca协议。
数据类型
ca协议支持基本的数据类型,以及对waveform支持的数组类型
- 字节类型:bi、mbbi等
- short:内部使用
- int:内部使用
- float:内部使用
- double:ai、ao等
- 字符数组:waveform
ps. 在虚拟回路概念下,实际收包可能是多个指令数据包的组合,可能某个包被截断,因此接收方处理recvBuffer数据时特别注意按长度拆包解包。
数据通信
Beacon消息
服务端启动后,将包含ip地址的udp数据包(CA_PROTO_RSRV_IS_UP
),通过5064端口(中继到repeater的5065端口)广播出去。这样不间断的对外发送心跳,是一种典型的keepalive机制,让网络上客户端得知服务端还存活。如果客户端超时未接收到beacon心跳指令,其监听的回调函数得知事件后,发送echo指令CA_PROTO_ECHO
(tcp)确认服务端是否真的断线。如果超时未收到服务端的确认回复,那么客户端主动断开与服务端的socket,释放通道分配的资源(包括线程,网络资源,数据结构等)。
Search消息
客户端启动后,会主动搜索服务程序端口,即会产生类似“pv_XXX在哪里”的搜索报文。搜索消息(CA_PROTO_SEARCH
)是通过5064端口,以UDP的形式广播出去的。收到广播的服务器,会查询自身数据库中是否包含对应的pv。如果没有,直接忽略。如果有,则将自身的ip地址和端口通过5064端口,以UDP的形式回复给客户端。
client收到回复,则建立起与服务端的tcp虚拟回路信道。对于多个pv在同一server上,一旦某个pv触发建立了信道,其余的pv将复用这条通道。
建立Channel
一旦client与server的虚拟回路形成,client会在这条信道上发送CREATE_CHANNEL消息(CA_PROTO_CREATE_CHAN
),希望建立pv的通道。create消息包含client的pv名字,以及channelID。建立成功,server会回复包含serverID(SID
)、channelID(CID
)、pv数据类型,以及数据个数的消息,通常一条虚拟回路仅分配一个serverID。并且在通信过程中保持不变,除非断线重连。
注册Repeater
repeater的扮演者中继器的角色,广播消息都会通过它转发。repeater是独立的进程,通常是被动创建的。client启动时,在和host交互之前,通常会试探性的绑定到repeater的5065端口,探测repeater是否存在。如果绑定失败,client假设repeater已经存在,可以正常通信。绑定成功,则是repeater不存在,client触发repeater进程的创建。
repeater创建后,client发送包含IP地址的注册消息(CA_REPEATER_REGISTER
),以后repeater收到的消息都会转发到该client上。
订阅数据
channel建立以后,client发送包含订阅ID的tcp消息(CA_PROTO_EVENT_ADD
)到server,订阅该pv的数据变更。变更类型有数值、警告和日志更新。在server端注册成功后,会回复成功消息。如此,关于pv数据的订阅发布模型建立,相关的数据变更将会通知到client。
读/写请求
当client有读写请求的时候,发送CA_PROTO_READ_NOTIFY
和CA_PROTO_WRITE_NOTIFY
消息,等待server处理完成。每次消息会包含递增的请求ID(IOID
),识别不同的请求。
流量控制
当client的处理能力不足以消化收到的数据更新时,会发送CA_PROTO_EVENTS_ONFF
消息到server,暂停向该client更新消息。直到CA_PROTO_EVENTS_ON
再次发送,恢复数据更新。
应用层调用
连接管理
epics通过ca_create_channel()
来创建通信的信道,包含创建的pv名,以及可选的回调函数。如果callback指针为空,那么需要不停的poll或者pend来等待执行结果。通常建议搭配connectionHandler,当有连接变化的时候,主动触发回调函数执行。在创建channel的过程中,会伴随信道的创建过程。
上下文资源
ca_context_create()
用来创建channel所需要的上下文。包含get/put/monitor的回调函数、线程资源、socket以及其他需要的资源。ca_context_create()
实际是有一个参数的,表示回调函数是否可以抢占式调用。对于抢占式调用,特别需要注意资源的保护,避免其他线程同时操作同一数据结构。另外,ca_client_context
资源的分配需要在任何实际ca操作之前,如果没有,默认会创建非抢占式的context。
线程管理
在单线程模式下,所有的channel会共用同一个context,资源能够最大化利用,但对于程序的执行效率会有影响。在多线程模式下,一般而言,每个channel都会创建自己的context。这种方式能更好的更简单的执行,每个线程完成独立的工作即可,但是会存在一定的资源浪费。因此,推荐使用一个context,其他线程附加到创建context的线程上。需要注意的是,这些线程必须是抢占式的,而独占式回调更适合单线程或者需要等待完成的任务。
网友评论