清空和阻塞
当CA客户端库不等待每次请求后从服务器返回的响应时,可以实现性能的显著提高。请求与CA服务器交互的所有请求被累积(缓存),并且在在ca_flush_io(), ca_pend_io(), ca_pend_event()或者ca_sg_block()被调用前不被转发给IOC,使得若干操作可以通过网络一起被高效地发送。在从ca_pend_io()接收到ECA_NORMAL前,不应该引用由ca_get()写入你程序变量的任何过程变量。
状态代码
如果成功,这里描述的例程返回状态代码ECA_NORMAL。从客户端库返回的失败的状态代码在本手册中与每个例程一起被列出。对客户端视乎有效的操作在服务器中仍然会出错。写字符串"off"到一个浮点字段是一个这种错误类型的示例。如果一个通道的服务器位与与客户端不同地址空间,则与服务器通信的ca_xxx()操作返回一个状态,它表明请求的有效项以及它是否成功地排队到这个服务器,但结束状态地通信被延时到一个用户回调被调用,或者缺少那,一个异常处理程序被调用。一个错误数值和错误的严重性被嵌入到一个CA状态(错误)常数。程序不应该通过检测返回的值是否是0(如UNIX规则),测试一个CA函数调用是否成功。以下是若干测试CA函数返回的函数。有关这个话题,更多信息见ca_signal()和SEVCHK()。
status = ca_XXXX();
SEVCHK( status, "ca_XXXX() returned failure status");
if ( status & CA_M_SUCCESS ) {
printf ( "The requested ca_XXXX() operation didn't complete successfully");
}
if ( status != ECA_NORMAL ) {
printf("The requested ca_XXXX() operation didn't complete successfully because \"%s\"\n",
ca_message ( status ) );
}
通道访问数据类型
CA通道在过程变量和客户端程序直接安形成一个虚拟电路。使用CA服务器库连接各种各样的数据源到EPICS是可能的。当一个CA通道与一个EPICS输入输出控制器(IOC)通信时,一个字段是一个PV的专属化,并且一个EPICS记录是一个包含字段的兼容功能块,并且以下元数据经常通过EPICS记录支持被映射成在EPICS记录内特定字段。
类型chtype的参数指定你想要传递的数据类型。它们期望在db_access.h中定义的DBR_XXXX数据类型代码集合之一。有对应所有C原始类型的数据类型,并且也有复合(C结构体)类型,包括各种过程变量属性,诸如单位,限制,时间戳或者警报状态。原始C类型遵循一个命名规则:C typedef dbr_xxxx_t对应DBR_XXXX数据类型代码。复合(C结构体)类型遵循一个命名规则,C结构体标签dbr_xxx对应DBR_XXX数据类型代码。以下表格更详细地提供了CA数据类型空间地结构体。由于数据地址以无类型"void *"指针被传递给CA客户端库,则应该小心确保你传递了对应你已经指定地DBR_XXXX类型的正确C数据类型。在db_access.h中提供了架构无关类型来帮助程序员编写可移植代码。例如,"dbr_short_t"应该用于发送或接收类型DBR_SHORT。注意:为了少混淆类型名DBR_SHORT,类型名DBR_INT已经被弃用。实际中,DBR_INT类型代码和DBR_SHORT类型代码都指向一个16位整数类型,并且功能上相同。
通道访问原始数据类型
通道访问数据类型空间的结构体
CA类型代码 | 读/写 | 原始C数据类型 | 过程变量属性 |
DBR_<PRIMITIVE_TYPE> | RW | dbr_<primitivetype>_t | 值 |
DBR_STS_<PRIMITIVE_TYPE> | R | struct dbr_sts_<priimitivetype> | 值,警报状态,和警报严重性 |
DBR_TIME_<PRIMITIVETYPE> | R | struct dbr_time_<primitivetype> | 值,警报状态,警报严重性和时间戳 |
DBR_GR_<PRIMITIVETYPE> | R | struct dbr_gr_<primitivetype> | 值,警报状态,警报严重性,单位,显式精度和图形限制 |
DBR_CTRL_<PRIMITIVETYPE> | R | struct dbr_ctrl_<primitivetype> | 值,警报状态,警报严重性,单位,显示精度,图形限制和控制限制 |
DBR_PUT_ACKT | W | dbr_put_ackt_t | 用于全局警报确认。瞬态警报必须被确认?(0,1)表示(no, yes) |
DBR_PUT_ACKS | W | dbr_put_acks_t | 用于全局警报确认。要确认最高警报。如果当前警报严重性低于或等于这个值,这个警报被确认。 |
DBR_STSACK_STRING | R | struct dbr_stsack_string | 值,警报状态,警报严重性,ackt,acks |
DBR_CLASS_NAME | R | dbr_class_name_t | 封闭接口的名称(如果通道连接到运行时间数据库的EPICS,记录的名称) |
通道值数组也可以被包含在结构化CA数据类型内。如果请求了多个元素,则通过索引指向DBR_XXX结构体中值字段的指针,在程序中可以访问各自元素。例如,以下代码加u四年一个数组过程变量中元素之和并且打印它的时间戳。dbr_size_n()函数可以用于确定一个结构化CA数据类型中有多个值元素时要保留的正群字节数目。
#include <stdio.h>
#include <stdlib.h>
#include "cadef.h"
int main ( int argc, char ** argv )
{
struct dbr_time_double * pTD;
const dbr_double_t * pValue;
unsigned nBytes;
unsigned elementCount;
char timeString[32];
unsigned i;
chid chan;
double sum;
int status;
if ( argc != 2 ) {
fprintf ( stderr, "usage: %s <channel name>", argv[0] );
return -1;
}
status = ca_create_channel ( argv[1], 0, 0, 0, & chan );
SEVCHK ( status, "ca_create_channel()" );
status = ca_pend_io ( 15.0 );
if ( status != ECA_NORMAL ) {
fprintf ( stderr, "\"%s\" not found.\n", argv[1] );
return -1;
}
elementCount = ca_element_count ( chan );
nBytes = dbr_size_n ( DBR_TIME_DOUBLE, elementCount );
pTD = ( struct dbr_time_double * ) malloc ( nBytes );
if ( ! pTD ) {
fprintf ( stderr, "insufficient memory to complete request\n" );
return -1;
}
status = ca_array_get ( DBR_TIME_DOUBLE, elementCount, chan, pTD );
SEVCHK ( status, "ca_array_get()" );
status = ca_pend_io ( 15.0 );
if ( status != ECA_NORMAL ) {
fprintf ( stderr, "\"%s\" didn't return a value.\n", argv[1] );
return -1;
}
pValue = & pTD->value;
sum = 0.0;
for ( i = 0; i < elementCount; i++ ) {
sum += pValue[i];
}
epicsTimeToStrftime ( timeString, sizeof ( timeString ),
"%a %b %d %Y %H:%M:%S.%f", & pTD->stamp );
printf ( "The sum of elements in %s at %s was %f\n",
argv[1], timeString, sum );
ca_clear_channel ( chan );
ca_task_exit ();
free ( pTD );
return 0;
}
用户提供的回调函数
在一个响应达到时,某个CA客户端初始化的请求在客户端进行中异步执行一个程序提供的回调。函数ca_put_callback(), ca_get_callback()和ca_create_subscription()都通过这种机制请求异步完成的通知。event_handler_args结构体通过值被传递给程序提供的回调。在这个结构体中,dbr字段是一个指向可能被返回的任何数据的void指针。status字段将被设置成在caerr.h中CA错误代码之一,并且将表示在IOC中运行的状态。如果状态字段未被设置为ECA_NORMAL或者数据未被正常地从这个操作返回(即:put回调),则你应该预计dbr字段将被设置为一个null指针(0)。当程序进行请求时,字段usr,chid和type被设置为指定的值。dbr和其指向的任何数据仅在在用户的回调函数内执行时才有效。
typedef struct event_handler_args {
void *usr; /* user argument supplied with request */
chanId chid; /* channel id */
long type; /* the type of the item returned */
long count; /* the element count of the item returned */
const void *dbr; /* a pointer to the item returned */
int status; /* ECA_XXX status of the requested op from the server */
} evargs;
void myCallback ( struct event_handler_args args )
{
if ( args.status != ECA_NORMAL ) {
}
if ( args.type == DBR_TIME_DOUBLE ) {
const struct dbr_time_double * pTD =
( const struct dbr_time_double * ) args.dbr;
}
}
通道访问异常
当服务器探测到错误,并且没有连接这个请求的客户端回调函数,在客户端中一个异常处理程序被执行。默认的异常处理程序在console上打印一条消息并且如果异常情况严重则退出。在CA客户端库内被SEVCHK宏探测到的某些内部异常,和错误可能也使得这个异常处理程序被调用。要修改这种行为,见ca_add_exception_event()。
服务器和客户端在相同主机上共享相同地址空间
如果过程变量的服务器和其客户端位于相同内存地址空间和相同主机内,则ca_xxx()操作绕过服务器并且直接与服务器工具组件(通常IOC的功能块数据库)交互。在这种情况中,ca_xxx()例程经常把请求操作的结束状态直接返回给调用者,没有机会给通过异常处理程序的异步错误通告。同样,回调可能直接被请求它们的CA库函数调用。
数组
对于需要一个指定数组元素数目的参数的例程,不可以请求比过程变量本地元素数目更多的数目。过程变量的最大本地元素数目在通道连接时从ca_element_count()获取。如果请求元素数目少于过程变量的本地元素数目,被请求值将从元素0开始获取。默认,CA限制数组中元素数目不多于近似16K除以数组中一个元素大小的数目。从EPICS 3.14开始,在客户端和服务器中可以配置这个最大数组尺寸。
连接管理
程序应该认为CA服务器可以被重启,以及网络连接是瞬态的。当你创建一个CA通道,其初始连接状态最常见是断开的。如果过程变量的服务器可达,这个库将立即初始化必要操作来与它进行连接。否则,客户端库将监视网络上服务器的状态并且在它变得可用时连接或重连这个过程变量的服务器。在通道连接后,程序可以通过这个通道自由执行IO操作,但应该预计这个通道在任何时刻由于网络连接中断或者服务器重启而断开。
三种方法可以用于确定一个通道是否连接:程序可以 调用ca_state()获取当前连接状态,在通道连接前在ca_pend_io()中阻塞,或者在其调用ca_create_channel()时安装一个连接回调处理程序。ca_pend_io()方法是最适合短运行持续时间的简单命令行程序,而连接回调方法最适合有长运行持续时间的toolkit组件。ca_state()的使用仅适合首选查询连接状态变化而不是选择异步通知的程序。ca_pend_io()函数仅阻塞指定null连接处理程序回调函数的通道。如果CA客户端和CA服务器都被托管在相同地址空间(在相同进程内),用户的连接状态变化函数将立即从ca_create_channel()内被运行。
线程安装和对用户代码的抢占式回调
从EPICS R3.14开始,CA客户端库在所有OS上都是线程安全的(在以前发行版中,仅在vxWorks上库才是线程安全的)。当客户端库被初始化时,程序员可以指定是否启用抢占式回调。抢占式回调默认被禁用。如果启用了抢占式回调,当主启动通道访问线程未在通道访问库中一个函数中时,用户的回调函数可能被CA的辅助线程调用。否则,仅在主启动通道访问线程在CA客户端库内执行时,用户的回调函数才将被调用。当CA客户端库调用用户的回调函数时,在执行另一个回调函数前,它总是等待当前回调结束。启用抢占回调的程序员应该熟悉使用互斥锁来创建一个可靠的多线程程序。
设置一个传统的单线程客户端,你将需要像这样的代码(见ca_context_create()和CA客户端上下文和程序特定的辅助线程)。
SEVCHK ( ca_context_create(ca_disable_preemptive_callback ), "application pdq calling ca_context_create" );
设置抢占式回调启用的CA客户端上下文,你将需要像这样的代码(见ca_context_create()和CA客户端上下文和程序特定的辅助线程)。
SEVCHK ( ca_context_create(ca_enable_preemptive_callback ), "application pdq calling ca_context_create" );
CA客户端上下文和程序特定的辅助线程
在相同地址空间(进程中)运行的若干客户端工具相互独立经常是必要的。例如,数据库CA链接和sequencer被设计成不使用相同CA客户端库线程,网络回路,以及数据结构。每个线程首次直接调用ca_context_create()直接或者在首次调用任何CA库函数时隐式地创建一个CA客户端库上下文。一个CA客户端库上下文包含连接和与CA客户端程序创建的通道所需的线程,网络回路和数据结构。由CA客户端库产生的辅助线程的优先级固定地偏移调用ca_context_create()的优先级。一个程序特定的辅助线程可以通过使用从ca_current_context()返回的CA上下文标识符调用ca_attach_context()加入这个CA上下文。一个要被加入的上下文必须是抢占式,必须使用ca_context_create(ca_enable_preemptive_callback)创建它。连接一个线程到一个显式或隐式用ca_create_context(ca_disable_preemptive_callback)创建的非抢占式CA上下文是不可能的。一旦一个线程加入了一个CA上下文,它只需要进行常规ca_xxx()库调用来使用这个上下文。
在销毁任何通道或连接它的程序特定线程后,通过调用ca_context_destroy()一个CA客户端库上下文可以被关闭和清理。上下文件可以被不同线程创建和销毁,只要它们都是相同上下文的组成。
从单线程程序查询CA客户端库
如果抢占式回调未被启用,则为了合适的操作,CA必须周期地被查询来管理后台活动。这要求你地程序必须在ca_pend_event(), ca_pend_io()或ca_sg_block()之一中等待,或者要么它应该至少每100毫秒调用ca_poll()。在单线程程序中,一个像Xt的文件描述符管理器或者在fdManager.h中描述的接口可以用于监视鼠标点击和CA的文件描述符,因而当CA服务器消息通过网络到达时,ca_poll()能立即被调用。
避免模仿仍然常见的坏习惯
EPICS发行初期,通过使用存储在类型chid中指针访问一个结构体中字段,检查一个通道的连接状态其本地类型和其本地元素数目是常见习惯。同样,在每个通道结构体中的用户私有指针也常通过访问通道结构体中字段直接被设置。由这个习惯产生了很多问题。例如,在3.13发行版前,意识到在每个通道结构中某个私有字段的瞬态变化使得直接使用私有字段可靠地测试通道连接状态是困难的。因而,在3.13发行版中,某些字段的名称被更改来阻止这种用法。从3.14发行版开始,这种方式编写的代码将编译不了。打算在大范围EPICSs版本上保持最高可移植性的代码应该尤其小心。例如,你应该用ca_element_count(channel_id)替代所有channel_id->count。这种方法应该在现在所有在用EPICS上可靠。ca_puser(chid)=xxxx尤其是一个问题。设置每个通道私有指针的最好机制是在创建这个通道时传递用户私有指针。在所有版本上都实现这种方法。另外,你也可以使用ca_set_puser(CHID, PUSER),但这个函数仅在EPICS 3.13官方发行版后才可用。
从POSIX信号处理程序调用CA函数
如你所预计,从POSIX信号处理程序调用CA客户端库是不安全的。同样,从中断上下文调用CA客户端库是不安全的。