第五章:订阅
PPS客户端可以订阅多个对象,而PPS对象也可以有多个订阅者。当发布者更改了一个对象时,订阅了该对象的所有客户端都将被告知。
为了订阅一个对象,客户端只需调用对象的open()操作,并使用O_RDONLY标志。或者使用O_RDWR标志来进行发布和订阅。之后订阅者就可以使用read()操作来查询对象。read()操作将返回所读数据的长度,以字节为单位。
每个PPS对象最多只能打开200个文件描述符。
PPS读取行为不同于标准的POSIX行为。对于PPS来说,如果分配的读取缓冲区对于读取的数据来说太小了,读取不会返回部分结果; 直接返回失败。
如果有多个订阅者从对象上读取也是安全的,因为pps管理器保证了每个pps read()都是原子的。
对于属性顺序:PPS不保证将按照属性写入对象的顺序来读取属性。也就是说,发布者可以这样写:
而订阅者可以按以下方式读取:
关于如何解析属性,后续API章节会进行讲述。
一:在文件描述符上等待数据
通常情况下,Polling PPS数据并不是一个好主意。在文件描述符上等待数据,最好使用以下机制:
(1)使用一个阻塞read ()
(2)用来接收指定事件的ionotify()机制
(3)select()函数
阻塞read()是最简单的机制。通常,如果希望在同一进程中合并来自文件描述符的输入和QNX Neutrion的消息传递,可以使用ionotify()。当从套接字、管道、串口等处理多个文件描述符时,请使用select()。
1.1 使用Block Read()
阻塞read()会一直等待,直到对象或其属性发生变化,才返回数据。若想使用阻塞read,您需要使用带有?wait的路径名选项来打开对象,该选项附加为对象的路径名的后缀。例如,打开播放列表对象:
对于默认的非阻塞读取,使用路径名:“/pps/media/PlayList”
对于阻塞读取,使用路径名加上选项:“/pps/media/PlayList?wait”
订阅者中的典型循环是驻留在自己的线程中。对于使用?wait选项打开对象的订阅者,此循环可能执行以下操作:
/* Assume that the object was opened with the ?wait option No error checking in this example. */
for(;;) {
read(fd, buf, sizeof(buf)); // Read waits until the object changes.
process(buf);
}
如果你打开了一个没有?wait选项的对象,并且想要改变为阻塞读取,那么你可以使用fcntl()清除O_NONBLOCK位:
flags = fcntl(fd, F_GETFL);
flags &= ~O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
或者使用ioctl()操作
int i=0;
ioctl(fd,FIONBIO,&i);
1.2 使用ionotify()
PPS服务实现了ionotify()功能,允许订阅者通过脉冲、信号、信号量等请求通知。在收到更改通知时,必须向对象文件发出read()以获取对象的内容。例如:
/* Process events while there are some */
while ( ( flags = ionotify( fd, _NOTIFY_ACTION_POLLARM, _NOTIFY_COND_INPUT, event ) != -1 ) && (flags & _NOTIFY_COND_INPUT) )
{
nbytes = read(fd, buf, sizeof(buf));
if ( nbytes > 0 )
process(buf);
}
/* If flags != -1, the event will be triggered in the future to get our attention */
1.3 使用select()
select()函数检查一组文件描述符,看它们是否可以读写。要对PPS使用select(),需要设置一个包含PPS对象的文件描述符的fd_set。您也可以选择设置超时时间限制。例如:
FD_ZERO( &readfds );
FD_SET( fd, &readfds );
switch ( ret = select( fd + 1, &readfds, NULL, NULL, &timeout ) )
{
case -1:
/* An error occurred. */
break;
case 0:
/* select() timed out. */
break;
default:
if( FD_ISSET( fd, &readfds ) )
{
num_bytes = read( fd, buf, sizeof(buf) );
if (num_bytes > 0)
process(buf);
}
}
二:订阅模式
订阅者可以同时以完全模式、增量模式或完全和增量模式打开对象。默认为full模式。要以增量模式打开对象,需要使用?delta 路径名open选项打开对象,该选项附加为对象路径名的后缀。
2.1 Full模式
在full模式(默认)中,订阅者总是接收到整个对象的单一、一致的版本,因为该对象在请求时才存在。
如果发布者在订阅者请求之前多次更改了对象,则订阅者仅在请求时才接收该对象的状态。如果对象再次被更改,则将再次通知订阅者更改。因此,在full模式下,订阅者可能会错过对对象的多个更改(在订阅者请求之前对该对象的更改)。
2.2 增量模式
在增量模式中,订阅者只接收对对象属性的更改(而不是所有更改)。
在第一次读取时,由于订阅者不知道对象的状态,所以PPS假定一切都已更改。因此,订阅者在增量模式下的第一次读取将返回对象的所有属性,而后续读取只返回自该订阅服务器上一次读取以来的更改。
因此,在FULL模式中,订阅者总是接收对对象的所有更改。
下图演示了发送给以full模式和delta模式打开PPS对象的订阅者的不同信息。
在所有情况下,PPS都维护带有状态的持久对象——始终存在一个对象。用于打开对象的模式不会改变对象本身;它仅确定订阅方对对象更改的视图。
增量模式队列
当订阅者以增量(delta)模式打开对象时,PPS服务将创建一个新的对象更改队列。也就是说,如果多个订阅者以增量模式打开一个对象,每个订阅者都有自己的对象更改队列,PPS服务将向每个订阅者发送自己的更改副本。如果没有订阅者在delta模式下打开对象,PPS服务将不维护该对象的任何更改队列。
在shutdown时,PPS服务会保存它的对象,但是对象的delta队列会丢失。
更改多个属性
如果发布者通过单个write()调用更改多个属性,那么PPS将把所有增量(delta)放在一起,并在订阅者的read()调用中的同一组中返回它们。换句话说,PPS delta维护了更改的时间和原子性。例如:
三: 订阅多个对象
PPS支持多个特殊对象以方便订阅多个对象:
Open this special object: To receive notification of changes to:
.all Any object in the directory
.notify Any object associated with a notification group
3.1 订阅目录下的所有对象
PPS使用目录作为一种自然的分组机制来简化和提高订阅多个对象的效率。可以通过调用open()然后在它们的文件描述符上select()来打开多个对象。更容易的是,您可以打开特殊的.all对象,它将所有对象合并到其目录中。
例如,假设/pps下的对象文件结构如下:
如果你打开rea/left/.all , 当rear/left目录下的任何对象改变时,你都将会收到一个通知。full模式下的read每次读取最多返回一个对象。
但是,如果以增量模式打开.all对象,则会收到目录中任何对象更改的每个属性的队列。在这种情况下,一个read()调用可能包含多个对象。
3.2 通知组
PPS提供了将一组文件描述符与通知组关联的机制。此机制允许您仅读取PPS特殊通知对象,以接收与该通知组关联的任何对象的更改通知。
3.2.1 创建通知组
要创建一个通知组:
1. 在PPS文件系统的根目录下打开.notify对象。
2. 读取.notify对象; 对该文件的第一次读取将返回一个短字符串(少于16个字符),其中包含其他文件描述符应该与之关联的组的名称。
要将文件描述符与组关联,在打开状态下指定路径名的opent选项?notify=group:value,其中:
1. group是第一次从.notify文件读取返回的字符串
2. value是任意字符串;订阅者将使用此字符串确定绑定到通知组的哪些对象具有可供读取的数据
返回的通知组字符串有一个尾部换行字符,在使用该字符串之前必须删除该字符。
3.2.1 使用通知组
一旦创建了通知组和与之关联的文件描述符,就可以使用此组了解与之关联的任何对象的更改。
只要组的任何文件描述符上有可供读取的数据,对通知对象的文件描述符的读取就会返回?notify=group:value 路径名选项中传递的字符串。
例如,将PPS挂载在/ PPS上,您可以编写如下内容:
char noid[16], buf[128];
int notify_fd, fd1, fd2;
notify_fd = open("/pps/.notify", O_RDONLY);
read(notify_fd, &noid[0], sizeof(noid));
sprintf(buf, "/pps/fish?notify=%s:water", noid);
fd1 = open(buf, O_RDONLY);
sprintf(buf, "/pps/dir/birds?notify=%s:air", noid);
fd2 = open(buf, O_RDONLY);
while(read(notify_fd, &buf, sizeof(buf) > 0) {
printf("Notify %s\n", buf);
}、
在上面的例子中,while循环输出的数据如下所示:
Notify 243:water
Notify 243:water
Notify 243:air
Notify 243:water
Notify 243:air
当从绑定到通知组的对象上进行读取时,订阅者应该对指定的每个更改执行多次读取。一个项上可能有多个更改,但不能保证每个更改都会在通知组的文件描述符上显示出来。
如果作为通知组一部分的对象的文件描述符被关闭,则随更改通知传递的字符串将以减号(“-”)作为前缀。例如:
-243:air