二.脉冲信号
在一些场合客户端不能阻塞等待服务器应答,例如中断处理函数,时钟函数,等。这种非阻塞的消息传递我们称之为脉冲。脉冲信号有如下特点:
1)有效传递40位数据(8位脉冲码,32位数据)
2)对发送者而言是非阻塞的
3)可以像其他消息一样被接受
4)脉冲会排队,只要接受者不是阻塞等待脉冲。
1.接受脉冲消息
可以利用MsgReceive函数接受消息,跟普通消息没有区别,但是不能用MsgReply,毕竟脉冲是异步的,不需要应答。MsgReceivePulse函数专门用来处理脉冲。如果程序调用MsgReceive返回值是0表明接受到的是一个脉冲信号。
例如:
#include
rcvid = MsgReceive (chid, …);
if (rcvid == 0) { // it's a pulse
// determine the type of pulse
// handle it
} else { // it's a regular message
// determine the type of message
// handle it
}
2.脉冲结构提体定义:
struct _pulse {
uint16_t type;
uint16_t subtype;
int8_t code;
uint8_t zero [3];
union sigval value;
int32_t scoid;
};
type和subtype为0,code和value组成40bits发送码。code指定脉冲类型,value携带数据。
value是一个联合体
union sigval {
int sival_int;
void *sival_ptr;
};
经常看到的典型应用是:
#include
rcvid = MsgReceive (chid, …
if (rcvid == 0) { // it's a pulse
// determine the type of pulse
switch (msg.pulse.code) {
case MY_PULSE_TIMER:
// One of your timers went off, do something
// about it...
break;
case MY_PULSE_HWINT:
// A hardware interrupt service routine sent
// you a pulse. There's a value in the "value"
// member that you need to examine:
val = msg.pulse.value.sival_int;
// Do something about it...
break;
case _PULSE_CODE_UNBLOCK:
// A pulse from the kernel, indicating a client
// unblock was received, do something about it...
break;
// etc...
} else { // it's a regular message
// determine the type of message
// handle it
}
3.脉冲接收函数
有时候我们只需要接受脉冲信号,例如,某个时刻,我们接受到客户端请求去做某事,但是我们不能立即给客户端作出响应,可能是我们在做一个长时间的硬件操作。在这种情况下,我们通常会给硬件设定一个定时器或则脉冲信号,当有事情发生。如果我们把服务器设计为经典的无线循环等待消息的模式,我们需要等待各种请求,这种设计是合理的,毕竟,服务器必须在一个时间服务多个线程。但是这样我们必须限制服务器数量。
在这种情况下,我们必须选择只是接受脉冲信号,而不是常规信号,这就是MsgReceivePulse函数的作用。
#include
int MsgReceivePulse (int chid,
void *rmsg,
int rbytes,
struct _msg_info *info);
4.如何设计需要接受脉冲信号和普通信号的服务器
通常我们设计一个线程池服务MsgReceive函数,对客户端请求作出响应。必须控制线程池线程数量,一些线程必须阻塞,等待脉冲到达,让线程池线程阻塞的典型方式是调用MsgReceivePulse函数,确保客户端请求会被及时察觉。
5.MsgDeliverEvent()函数的使用
之前我们提到发送消息时候的等级制度,有些情况我们需要打破,例如,客户端给服务器发送消息,服务器不能立刻应答而客户端不想等待。遇到这种情况,正确的做法是,告诉客户端,请求一段时间后会得到处理客户端得以继续运行,一旦服务器完成任务,服务器需要一些方式告诉客户端,请求完成。在发送等级的时候提到客户端不能给服务器发送消息,可能会导致死锁,如果同一时间客户端给服务器也发送消息请求。
解决这个问题需要多步操作。
1)客户端创建一个 struct sigevent 并且填充
2)客户端给服务器发送消息
3)服务器接受到消息存下结构体信息,以及Receive ID,立即对客户端应答。
4)客户端继续运行
5)当服务器完成任务调用 MsgDeliverEvent()通知客户端任务完成
int
MsgDeliverEvent (int rcvid,
const struct sigevent *event);
event服务器不需要作修改
rcvid是服务器从客户端接收的。当服务器给客户端应答,这个id将失去意义。
另外MsgDeliverEvent是非阻塞函数。
三、频道创建时候的标志位
_NTO_CHF_FIXED_PRIORITY
接受线程优先级不因为发送线程的优先级而发生改变。
_NTO_CHF_UNBLOCK
当客户端试图解除阻塞的时候,内核发送脉冲给服务器(_PULSE_CODE_UNBLOCK )
_NTO_CHF_THREAD_DEATH
当阻塞在频道内的一个线程死亡,内核发送脉冲(_PULSE_CODE_THREADDEATH )
_NTO_CHF_SENDER_LEN
内核把客户端消息的长度作为消息的一部分传递给服务器(srcmsglen)
_NTO_CHF_REPLY_LEN
内核把客户端应答消息缓存的长度作为消息的一部分传递给服务器(srcmsglen)
_NTO_CHF_COID_DISCONNECT
当进程内的所有连接都被终止了
_NTO_CHF_UNBLOCK详解:
我们知道,在消息传递中,为了保证可靠性,当
客户端向服务器发送消息时,客户端是被“阻塞”(BLOCK)住的。这种状态,要一直到服务器应答了以后才会被开放。这就造成了一个问题,有时候,如果服务器还没有应答,而客户端又希望从阻塞中恢复,那又怎么办呢? 最常见的,比如用户CTRL-C了客户端进程,按POSIX标准,一个SIGINT应该被发送到客户端进程,而客户端进程如果没有特别处理的话,应该被SIGINT终止。那么,如果这时,客户端正好在阻塞状态中,会怎么样呢? 如果客户端进程,就这样突然凭空消失的话,服务器端会混乱,因为服务器端的程序,是建立在客户端被阻塞的情形下的。试想一下,如果客户端突然消失了,而服务器端又要MsgRead(),或是MsgWrite()会发生什么事呢? 为了防止服务器端发生这种混乱,QNX规定如果一个进程,正REPLY
BLOCK在别的进程上的时候,它不能自动脱离BLOCK状态;相反地,如果进程有什么理由要退出BLOCK状态,系统会先给服务器端发送一个 “UNBLOCK”脉冲,好象是说“REPLY BLOCK中的xx线程希望脱离阻塞状态”;而由服务器自己判断应该如何处理。服务器端可以在自己内部数据结构做出整理后,选择MsgError(rcvid,
EINTR);以解除客户端的阻塞状态;也可以什么也不做,不理睬这个脉冲,这样客户端就不能退出BLOCK状态。有时候你会在QNX遇到Ctrl-c但进程却没有退出的情况,这就是因为进程正被阻塞在一个服务器上,而服务器没有正确处理
UNBLOCK PAULSE的结果。
四、同步问题
即使我们创建频道的时候设置了_NTO_CHF_UNBLOCK参数。实际情况下依然有一个同步问题需要我们处理。想象一下,服务器多个线程阻塞在MsgReceive函数,等待消息或者脉冲,这时候,客户端发送一个消息,其中一个线程接收了并开始工作。这期间,客户端希望解除阻塞状态,那么内核发送一个解除阻塞的脉冲消息。服务器其他线程接收到这个脉冲。这样第一个和第二个线程会发生竞争,如果第二个线程给客户端应答那么它会收到客户端对应的一些信息,这时候第一个线程应答客户端,可能这已经是客户端第二次请求了。另外一种情况是,第一个线程先应答,那么可能是把客户端第二次请求的阻塞解除了。通常解决这个问题的办法是用一个互斥锁。MsgReceive之后加锁MsgReply之前解锁。
五、消息在网络中的传递
55/5<12345