系列文章
目录
1.简介
QNX是一个微内核的操作系统,操作系统中,因为每一个进程都拥有自己的独立的进程虚拟地址空间,造成了进程独立性。但进程间总会需要进行交互,因此进程间的通信是难免的。常见的进程间通信有:socket、信号、信号量、andorid特有的binder通信,共享内存等。
本文主要介绍qnx操作系统的同步消息传递机制。
通信步骤和线程状态如下:
1.因为进程间消息的传递通过通道(channel)连接,所以首先服务端需要通过name_attach创建channel,然后调用MsgReceive接收消息。此时客户端没有发送消息,则服务端阻塞,服务端阻塞状态是RECEIVE blocked。
2.客户端调用name_open打开并连接服务端。
3.客户端调用MsgSend发送消息到服务端,若此时服务端没有调用MsgReceive(),则客户端状态是SEND blocked,若此时服务端调用了MsgReceive(),但是服务端还没调用MsgReply()/MsgError(),则客户端处于REPLY blocked。
4.服务端接收到客户端发送的消息后处理消息,此时服务端状态为ready状态。并调用MsgReply回复client。
1.1 交互图
1.2 状态迁移图
1.2.1 客户端迁移图
send blocked:当客户端调用Msgsend函数后,服务端并没有调用MsgReceive的状态。
reply blocked:当客户端调用Msgsend函数后,服务端也调用了MsgReceive,但服务端还未调用
MsgReply/MsgError的状态。
ready:当客户端调用Msgsend函数后,服务端也调用了MsgReceive和MsgReply/MsgError的状态。
1.2.2 服务端迁移图
receive blocked:服务端调用MsgRecevie,但是客户端没调用Msgsend的状态。
ready:服务端调用MsgRecevie,然后客户端调用Msgsend的状态。
2.方法介绍
2.1 name_attach
//在路径名空间中注册一个名称并创建一个通道
name_attach_t * name_attach( dispatch_t * dpp,
const char * path,
unsigned flags );
//dpp可以是null,或者是成功调用dispatch_create()或dispatch_ccreate_channel()返回的调度句柄。
//path是要在/dev/name/[local|global]/下注册的路径。
//flags 这是控制函数行为的标志位。如果是NAME_FLAG_ATTACH_GLOBAL,代表创建全局的附加名称。默认附在本地
//返回值:返回一个指向name_attach_t结构的指针,结构如下:
//如果失败,则返回为空
typedef struct _name_attach {
dispatch_t* dpp; //创建此连接时使用的调度句柄。
int chid; //直接用于MsgReceive()的通道ID。
int mntid;
int zero[2];
} name_attach_t;
2.2 name_detach
//函数的作用是从名称空间中删除名称,并破坏name_attach()创建的通道。
//如果在标志中设置NAME_FLAG_DETACH_SAVEPP,则NAME_attach_t结构中包含的调度指针不会被破坏;
//由您通过调用dispatchdestroy()来销毁它。默认情况是销毁调度指针。
int name_detach( name_attach_t * attach,
unsigned flags );
//attach:成功调用name_attach()返回的指向name_attach_t结构的指针。
//flag:NAME_FLAG_DETACH_SAVEPP--不要破坏调度句柄。
2.3 MsgReceive
//接收来自其他进程的消息,这些消息通常通过MsgSend函数发送,当调用MsgReceive时,它会阻塞调用线程,直到有消息可用或者发生错误为止。
int MsgReceive( int chid,
void * msg,
size_t bytes,
struct _msg_info * info );
//chid是已经建立链接的频道id
//msg指向要发送的消息的地址
//bytes,发送消息的大小
//info,可以为Null,或者指向_msg_info结构的指针,函数可以在该结构中存储有关消息的附加信息。
//返回值:
//-1,代表通信发生错误
//0代表是脉冲消息
//大于0,代表收到了客户端发送的消息
2.4 MsgReply
//当一个进程接收到另一个进程发送的消息后,它可以使用MsgReply函数来发送一个回复消息。
int MsgReply(int rcvid, int status, const void *reply, size_t rsize);
参数说明:
//rcvid:是接收消息的ID,通常这个ID是在调用MsgReceive时获得的。
//status:是一个整数,表示回复消息的状态码。通常,它用于指示消息处理的结果或状态。
//reply:是一个指向回复消息内容的指针。如果不需要发送任何数据,这个指针可以设置为NULL。
//rsize:指定了回复消息的大小,即reply指针指向的数据的大小。
//返回值:
//0代表发送成功。
//-1代表发送失败。
2.5 name_open
int name_open( const char * name,
int flags );
//name :连接服务端的频道名称,在服务端name_attach时会指定。
//flag:一些策略,如安全连接等。。
//返回值:
//-1,代表发生错误
//大于0,代表正确连接上服务端,返回的是通道连接id
2.6 MsgSend
int MsgSend(int coid, const void *smsg, int sbytes, void *rmsg, int rbytes);
//参数说明:
//coid:接收消息的通道的ID。
//smsg:指向要发送的消息的指针。
//sbytes:要发送的消息的字节数。
//rmsg:指向用于接收来自接收方的回复消息的缓冲区的指针(如果不需要回复,则此参数可以为NULL)。
//rbytes:接收缓冲区的大小(如果不需要回复,则此参数可以为0)。
//返回值:
//如果成功,MsgSend函数返回接收到的回复消息的字节数(如果提供了回复缓冲区的话)。
//如果失败,返回-1,并设置全局变量errno以指示错误原因。
3.如何使用?
3.1 服务端
步骤:
第一步:服务端通过name_attach,建立一个频道
第二步:自定义结构体,此结构体需和客户端定义一样,其中包含通信的信息。
第三步:通过MsgReceive,用自定义的结构体接受客户端消息。
第四步:解析消息,进行相应处理。
typedef struct
{
struct _pulse hdr;//表示脉搏心跳
uint32_t internalcmd;
uint32_t data1size;
uint8_t data1[MAX_DATAID_SIZE];
uint32_t data2size;
uint8_t data2[MAX_PAYLOAD_SIZE];
} stIpcMessage;//此结构体类型是自定义的,需要和客户端定义的结构体类型相同
void loop()
{
//第一步创建name_attach_t,建立一个频道
name_attach_t *nameattach;
if ((nameattach = name_attach(NULL, "testservice", 0)) == NULL)
//name_attach()就是用来建立一个频道,
并为频道注册一个名字testservice
//name_attach():是在服务器端使用,在名称空间中定义一个name(客户端中对应open这个name),
//同时创建了一个channel.
{
return;
}
int receiveid;
stIpcMessage ipcmsg;//第二步:自定义结构体,此结构体需和客户端定义一样,其中包含通信的信息。
//第三步:死循环不断的接受客户端的消息,用ipcmsg接收消息
while (true)
{
receiveid = MsgReceive(attach->chid, &ipcmsg, sizeof(ipcmsg), NULL);
//使用MsgReceive函数接收客户端的信息。attach->chid是channelid
if (receiveid == -1)//代表MsgReceive通信错误,退出循环
{
break;
}
if (receiveid == 0)//0表示收到是脉搏信息,>0表示收到message
{
switch (ipcmsg.hdr.code)
{
case _PULSE_CODE_DISCONNECT://客户端断开了所有连接(对于我们名称中的每个name_open()调用name_close())或终止
ConnectDetach(ipcmsg.hdr.scoid);//终止和客户端的连接
break;
case _PULSE_CODE_UNBLOCK://REPLY BLOCK中的xx线程希望脱离阻塞状态”。
//然后,服务器会根据自身的逻辑和规则来判断如何处理这个请求
break;
default:
break;
}
continue;
}
if (ipcmsg.hdr.type == _IO_CONNECT)
//当客户端用name_open连接上服务端的时候,会发送一个connect的消息
{
MsgReply(receiveid, EOK, NULL, 0);
continue;
}
//收到的一些错误的message
if (ipcmsg.hdr.type > _IO_BASE && ipcmsg.hdr.type <= _IO_MAX)
{
MsgError(receiveid, ENOSYS);
continue;
}
switch (ipcmsg.internalcmd)//判断msg的字段
{
case REQ_A://是A类型请求,则
{
//第四步:处理消息
std::string data1((char*)&ipcmsg.data1[0], msg.data1size);/
MsgReply(rcvid, EOK, NULL, 0);//向客户端回复收到消息
}
break;
}
}
int main()
{
loop();
}
3.2 客户端
步骤:
第一步:通过name_open连接服务端,传入的参数正是服务端注册的频道的名称。
第二步:封装自定义的结构体,此结构体和服务端一样,通过MsgSend发送数据。
第三步:发送完后,可以关闭频道。
typedef struct
{
struct _pulse hdr;//服务端会根据值判断传递的消息是否出错。
uint32_t internalcmd;//服务端根据此字段判断是什么类型
uint32_t data1size;
uint8_t data1[MAX_DATAID_SIZE];
uint32_t data2size;
uint8_t data2[MAX_PAYLOAD_SIZE];
} IpcMessage;//此结构体类型是自定义的,需要和客户端定义的结构体类型相同
int ChannelId;
//第一步连接服务端
void connectservice()
{
ChannelId=name_open("testservice", 0));//此处需要对应服务端的名字
}
//第二步:封装结构体,发送数据
void sendmsg()
{
IpcMessage sendmsg;
data1 = "data1";
data2 = "data2";
memset(&sendmsg, 0x00, sizeof(IpcMessage)); // 重置 msg.hdr.type and msg.hdr.subtype to 0x00.
sendmsg.internalcmd = REQ_A;//是A类请求,自定的类型,用于区分是什么类型请求
sendmsg.data1size = data1.size();
memcpy(&sendmsg.data1[0], data1.c_str(), data1.size());
sendmsg.data2size = data2.size();
memcpy(&sendmsg.data2[0], data2.c_str(), data2.size());
bool ret = false;
if (MsgSend(ChannelId, sendmsg, ((uint8_t*)&sendmsg->data2 - (uint8_t*)&sendmsg) + data2->data2size, NULL, 0) != -1) {
//MsgSend(ConnectionId, SendBuf, SendLen, ReplyBuf, ReplyLen);
ret = true;
}
//发送完后,关闭ChannelId
name_close(ChannelId);
}
int main()
{
connectservice();
sendmsg();
}