QNX中进程间通信(IPC)之Connection篇

主要内容翻译自QNX开发文档,根据自己理解翻译,错误之处在所难免,目的还是提供给大家对照阅读,我自己也写了一些代码,抽空后继也会放上

 

使用某个对象建立连接的进程我们称之为 connector connector server 创建、拥有并供 client 连接上的名称。 Connector 仅被用来建立连接。

Connector 有一个数字形式的 ID 和一个相关联的名称,并且该名称和 ID 在其所在的 Photon session 中是唯一的,以下是可以使用名称的几种情况:

l  Server 启动时,创建一个带有约定名称的 connector( 如下面例子中的 Helpviewer) Client 连接到该 connector 、发送请求,然后关闭连接。如果 client 查找名称过程失败,将会自动创建 server 并且重试。

l  Server 创建一个匿名 connector 并且以某种方式发送 connector ID 到潜在的 client 对象 ( 拖拽事件使用这种方式 ) Client 之后使用这个 ID 连接 server

l  如果 client server 运行时又需要创建一些 server 供自己使用,可以使用 PtConnectionTmpName() 函数来创建一个“临时 connector 名称”并且通过命令行方式或者系统环境变量方式传递给 server ,之后连接上去。

1.1     Naming conventions

应该使用以下命名约定来定义 connector 的唯一名称:

l  名称中不得包含被 QNX 软件系统保留的斜线。

l  第三方产品应该以一个唯一字符串 ( 例如电子邮件地址或域名 ) 后跟随斜线加名称的方式,例如 www.acme.com/dbmanager

1.2     典型实例

以下是使用 connection 的典型实例:

1)        Server 调用 PtConnectorCreate() 来设置一个 connector ,指定 client 连接上 connector 的时候将回调哪一个函数。

2)        如果 client 需要一个 connector ID 来找到 connector server 需要调用 PtConnectorGetId() 来获取这个 ID ,并在以后需要时将这个 ID 传递给 client

3)        Client 通过调用 PtConnectionFindName() PtConnectionFindId() 函数来查询 connector ,如果函数执行成功将返回一个 PtConnectionClient_t 类型的 client connection 对象。 Client 也可以通过调用 PtConnectionWaitForName() 函数来尝试重复查找 server( 在指定限制时间内或直到 server 中止 )

4)        如果 client 找到 connector ,此时 library 将会对连接进行设置并且调用 server 指定的回调函数,同时传递 PtConnectionServer_t 类型的 server connection 对象到回调函数中。

5)        Server 回调函数使用 PtConnectionAddMsgHandlers() 来设置客户端传送消息的 handler( 处理函数 )

6)        Client 使用 PtConnectionSend() PtConnectionSendmx() 来发送消息,同时阻塞,直到 Server 回复。

7)        Server 对消息进行处理,并调用 PtConnectionReply() PtConnectionReplymx() 回复 client

8)        Client server 继续执行并进行消息传递。

9)        如果 Client 想中断连接,调用 PtConnectionClientDestroy() ;如果是 Server ,调用 PtConnectionServerDestroy()

10)    server 不需要 connector 的时候可以调用 PtConnectorDestroy() 销毁。

可以使用在消息传送 connection 中夹带数据。 Server 调用 PtConnectionServerSetUserData() 来指定 Client 可通过调用 PtConnectionClientGetUserData() 获取的数据,同理, Client 调用 PtConnectionClientSetUserData() 来设置 Server 可通过调用 PtConnectionServerGetUserData() 获取的数据。

Server 端通过 PtConnectionServerSetError() 设置错误处理函数, Client 端通过 PtConnectionClientSetError() 设置。

Server 也可以通过事件来通知 Client

1)        客户端通过 PtConnectionAddEventHandlers() 设置一个或多个消息处理函数,可以对不同消息类型分别进行处理。

2)        Server 可以通过调用 PtConnectionNotify() 发送事件通知,该函数在 server 自身 buffer 空间不够的时候通过轮流调用 PtConnectionFlush() 来处理。

3)        Server 可以通过 PtConnectionResizeEventBuffer() 改变缓冲区大小。

1.3     Local Connections

某些情况下你可能需要对某个进程创建一个自连接,这种情况下与标准的连接略有不同:

l  对于标准连接, PtConnectionSend() PtConnectionSendmx() PtConnectionNotify() 函数发送一个消息 (message) 或是脉冲 (pulse) ,不执行任何用户代码 (Photon 事件被正常处理导致 PtConnectionNotify() 需要填写缓冲区的情况除外 )

对于本地连接, PtConnectionSend() PtConnectionSendmx() PtConnectionNotify() 直接调用消息处理函数,调用代码和处理必须考虑这样的副作用。

l  另一个不同是处理从不同的上下文调用。通常,一个事件或消息处理源自于一个输入函数,由于输入函数直到代码执行到主循环或 PtBkgdHandlerProcess() PtProcessEvent() 后才会调用,可以假设在回调执行时处理不会调用因此是安全的。但如果 connection 是本地的,处理通过 PtConnectionSend() PtConnectionSendmx() PtConnectionNotify() 直接调用,因此需要做相应修改。

l  另一个副作用是如果消息处理由 PtConnectionNotify() 调用, client 在回复 (reply) 之前得到通知 (notification) ,也就是说, client 的事件处理在 PtConnectionSend() PtConnectionSendmx() 调用返回之前执行;如果这时 PtConnectionSend() PtConnectionSendmx() 再次执行调用返回错误, errno 被设置为 EBUSY ( 这样系统避免了进入死循环 )

简单的避免方法是在消息处理中避免发送通知――作为替代,通知可以放置于回复中。

 

1.1     Example

以下例子使用 connector 判断是否有重复执行实例,该实例有两个命令行开关:

-e

如果有实例运行,让他关闭。

-f file

如果有实例运行,让他打开一个指定的文件,否则新实例打开文件。

以下是关键代码:

 

 

/* Standard headers */ #include #include #include #include /* Toolkit headers */ #include #include #include /* Local headers */ #include "abimport.h" #include "proto.h" enum MyMsgType { MY_MSGTYPE_EXIT, MY_MSGTYPE_OPEN_DOC, MY_MSGTYPE_TOFRONT }; enum MyReplyType { MY_REPTYPE_SUCCESS, MY_REPTYPE_BADMSG }; struct MyMsg { char docname[ PATH_MAX ]; }; struct MyReply { enum MyReplyType status; }; /* Handle a message from a client: */ static PtConnectionMsgFunc_t msghandler; static void const *msghandler( PtConnectionServer_t *connection, void *data, unsigned long type, void const *msgptr, unsigned msglen, unsigned *reply_len ) { struct MyMsg const *msg = (struct MyMsg const*) msgptr; static struct MyReply reply; reply.status = MY_REPTYPE_SUCCESS; switch ( type ) { case MY_MSGTYPE_EXIT : PtConnectionReply( connection, sizeof(reply), &reply ); PtExit( EXIT_SUCCESS ); break; case MY_MSGTYPE_OPEN_DOC : reply.status = OpenNewDocument( msg->docname ); break; case MY_MSGTYPE_TOFRONT : break; default : reply.status = MY_REPTYPE_BADMSG; } PtWindowToFront( ABW_base ); *reply_len = sizeof(reply); return &reply; } /* Set up a new connection: */ static PtConnectorCallbackFunc_t connector_callback; static void connector_callback( PtConnector_t *connector, PtConnectionServer_t *connection, void *data ) { static const PtConnectionMsgHandler_t handlers = { 0, msghandler }; if ( PtConnectionAddMsgHandlers( connection, &handlers, 1 ) != 0 ) { fputs( "Unable to set up connection handler/n", stderr ); PtConnectionServerDestroy( connection ); } } /* Application Options string */ const char ApOptions[] = AB_OPTIONS "ef:"; /* Add your options in the "" */ /* Application initialization function */ int init( int argc, char *argv[] ) { struct MyMsg msg; int opt; long msgtype = MY_MSGTYPE_TOFRONT; const char *document = NULL; static const char name[] = "[email protected]/ConnectionExample"; while ( ( opt = getopt( argc, argv, ApOptions ) ) != -1 ) switch ( opt ) { case '?' : PtExit( EXIT_FAILURE ); case 'e' : msgtype = MY_MSGTYPE_EXIT; break; case 'f' : document = optarg; } if ( document ) if ( msgtype == MY_MSGTYPE_EXIT ) { fputs( "You can't specify both the -e and -f options/n", stderr ); PtExit( EXIT_FAILURE ); } else { msgtype = MY_MSGTYPE_OPEN_DOC; strncpy( msg.docname, document, sizeof(msg.docname)-1 ); } while ( PtConnectorCreate( name, connector_callback, 0 ) == NULL ) { /* If this failed, another instance of the app must be already running */ PtConnectionClient_t *clnt; if ( ( clnt = PtConnectionFindName( name, 0, 0 ) ) != 0 ) { struct MyReply reply; int result = PtConnectionSend( clnt, msgtype, &msg, &reply, sizeof(msg), sizeof(reply) ); PtConnectionClientDestroy( clnt ); if ( result == 0 ) PtExit( reply.status ); } } /* Since PtConnectorCreate() has succeeded, we're the only instance of the app running */ if ( msgtype == MY_MSGTYPE_EXIT ) { fputs( "Can't tell it to exit; it's not running/n", stderr ); PtExit( EXIT_FAILURE ); } if ( document ) OpenNewDocument( document ); return Pt_CONTINUE; }

 

 

1       发送 QNX 消息

Photon 应用可以使用 MsgSend() 传递消息,但接收方必须迅速 MsgReply() ,这是因为 Photon 事件在应用阻塞时不处理。 ( 当应用中包含多个线程处理事件时宾切你在调用 MsgSend() 之前调用 PtLeave() 并在之后调用 PtEnter() 。对于多线程编程,请参照 Parallel Operations chapter 这一章。

以下例子演示了从一个 text widget 获取字符串并且发送到另一进程,得到回复后显示在同一 text widget

 

 

/* Callback that sends a message to another process */ /* Standard headers */ #include #include #include #include #include /* Needed for MsgSend() */ /* Toolkit headers */ #include #include #include /* Local headers */ #include "globals.h" #include "abimport.h" #include "proto.h" extern int coid; int send_msg_to_b( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) { char *a_message; /* eliminate 'unreferenced' warnings */ widget = widget, apinfo = apinfo, cbinfo = cbinfo; /* Get the string from the text widget. */ PtGetResource (ABW_msg_text, Pt_ARG_TEXT_STRING, 0, 0); /* Send the string to another process. */ a_message = (char *)args[0].value; if ( MsgSend (coid, a_message, msg_size, rcv_msg, msg_size) == -1) { perror ("Send to B failed"); PtExit (-1); } /* Remember the UI is "hung" until the other process replies! */ /* Display the reply in the same text widget. */ PtSetResource (ABW_msg_text, Pt_ARG_TEXT_STRING, rcv_msg, 0); return( Pt_CONTINUE ); }

 

 

1       接收 QNX 消息

为了在 Photo 应用中处理非 Photon 消息,你必须要注册一个 input handling procedure( 输入处理函数,或 input handler) ,否则事件将不被处理。这是因为 Photon 事件处理循环中的 MsgReceive() 忽略了非 Photon 事件。

注意 :你可以创建自己的消息循环并在其中调用 MsgReceive() ,但请记住应用及其接口会一直阻塞直到某个进程发送消息。因此,使用前面讨论的 input handler 显然更好。

一个 input handler 负责对接收自另一应用程序的消息进行处理。当你将 input handler 注册到 widget 库时,你指定了 input handler 相关联的 pid

你可以在应用程序中对于一个 pid 定义多于一个 input handler 。当自进程接收到消息时, widget 库将从 input handler 列表中开始处理,并遵从“后定义先执行”的方式,直到一个 input handler 处理了该消息 ( 也就是,该处理不返回 Pt_CONTINUE) 。可以查看 Pt_CONTINUE 定义。

你可以通过指定 pid 0 来注册一个非特定的 input handler ,这个 input handler 在应用接收到以下消息时调用:

l  任意不含发送者 pid 的输入处理的非 Photon 消息。

l  任意包含发送者 pid 的输入处理的非 Photon 消息,该消息未被处理,即,前一输入处理返回 PT_CONTINUE

l  一个用户脉冲。 ( 例如,一个非负数的脉冲 )

1.1     Adding an input handler

在应用程序初始化的时候调用 PtAppAddInput() 来注册 input handler 。语法如下,更多信息参见 Photon Library Reference

PtInputId_t *PtAppAddInput(

                 PtAppContext_t app_context,

                 pid_t pid,

                 PtInputCallbackProc_t input_func,

                 void *data );

参数列表:

app_context

应用程序上下文地址,使用一个 PtAppContext_t 结构来管理应用相关联的数据,如果使用 NULL 作为参数,将使用默认上下文。

pid

将要处理该消息的进程 ID ,如果使用 0 作为参数,将会发送消息到所有进程。

input_func

PtInputCallbackProc_t 类型的处理函数,更多描述参见 Photon Library Reference

data

额外数据,用来传递参数。

PtAppAddInput() 返回一个 input handler ID 的指针,可以用来删除该输入处理。

input handler 的原型如下:

int input_proc( void *data,

                int rcvid,

                void *msg,

                size_t msglen );

参数意义:

data

你传递到该输入处理的额外数据的指针。

rcvid

消息发送时设置的接收进程 id

msg

发送的消息指针。

msglen

消息缓冲区大小,如果真实的消息大小超过缓冲区大小,可以使用 MsgRead() 读取剩余消息内容。

你也可以定义 input handler 类型为 PtInputCallbackProcF_t 来使用编译器的类型检查特性。

注意 :如果 input handler 修改了显示,请确保调用了 PtFlush() 来刷新。

一个 input handler 必须返回以下几个值中的一个:

Pt_CONTINUE

input handler 不能识别此消息。如果还有其他 input handler 关联到相同的进程 ID ,将会调用这些处理函数,如果没有,或者所有关联该进程 ID 的消息处理都返回 Pt_CONTINUE ,将会被转入 rcvid 0 的输入处理。如果所有 input handler 返回 Pt_CONTINUE ,该消息返回值被设置为 ENOSYS

Pt_END

该消息被识别、处理并且从 input handler 列表中删除,别的 input handler 不会被调用。

Pt_HALT

消息被识别、处理但不从列表删除,将交由另外的 input handler 函数处理。

1.1.1     name_attach() and PtAppAddInput()

如果可能,一般情况下你应该使用 connection 而非 name_attach() 来建立一个进程间连接。然而,以下情况你无法使用 Photon connection

l  连接的 client 端不是一个 Photon 应用。

l  连接的进程属于不同的 Photon session 。即使你在运行多个不同的 Photon 任务于一台计算机,或者你的 Photon 任务由多个运行于不同计算机的应用组成,并且正好两个相互通信的进程位于不同计算机。

PtAppAddInput() name_attach() 都尽力创建一个 _NTO_CHF_COID_DISCONNECT _NTO_CHF_DISCONNECT 集合的通道 ( 参见 QNX Neutrino Library Reference) 。如果你的应用程序调用了这两个函数,你需要让 Photon 使用与 name_attach() 相同的通道,该通道可以使用 PhChannelAttach() 获得,具体用法如下:

PhChannelAttach( chid, -1, NULL );

该调用必须在 name_attach() PtAppAddInput() 之前。

如果你想为 Photon 创建分离的 channel ,只要你创建并传递给 PhChannelAttach() 调用之前或在 name_attach() 调用之后,这都不是问题。但请记住,由于某些 Photon library 中的方法认为 Photon channel 拥有两个 DISCONNECT 标识,将可能不能正常工作。其中的一种情况是检测中止连接方法 ( 参见 PtConnectionClientSetError() and PtConnectionServerSetError()) 及其任何依赖项。

1.2     Removing an input handler

可以通过以下手段删除消息处理 input handler

l  返回 Pt_END

或者

l  调用 PtAppRemoveInput() 并且传递 PtAppAddInput() 返回的 ID 值。

1.3     Message buffer size

正如以上讨论的,在输入函数中包含以下参数:

msg

指向一个接收消息的缓冲区的指针。

msglen

缓冲区大小。

缓冲区可能不够保存完整消息内容,一个解决方法是使用前几个字节来标识消息类型来判断消息内容的大小,当你知道消息大小后,就可以:

l  使用 MsgReadv() 获取完整消息内容。

或者

l  拷贝你获取到的缓冲区部分到一个新的缓冲区,之后调用 MsgReadv() 获取剩余部分。添加剩余部分到前面以接收到的部分组成完整内容。

作为选择,你可以设置事件处理缓冲区为应用程序可能接收消息内容的最大值 ( 如果预先可知的话 ) ,这可以通过调用 PtResizeEventMsg() 来实现,当然你必须在接收到任何消息之前设定这个值。

注意 PtResizeEventMsg() 不会将缓冲区大小设置少于某个系统定义的最低值。

1.4     Example — logging error messages

以下代码展示对于非特定消息的应用:用来做任意进程的错误记录。当接收到消息时,应用程序将消息内容显示于 multiline text widget 中。 ( 示例中的 log_message 在别处定义 )

 

 

int input_proc(void *client_data, int rcvid, void *msg, size_t msglen) { struct log_message *log = (struct log_message *)msg; /* Only process log messages */ if (log->type == LOG_MSG) { PtWidget_t *text = (PtWidget_t *)client_data; struct log_message header; int msg_offset = offsetof(struct log_message, msg); int log_msglen; int status; /* See if our entire header is in the buffer -- it should be */ if (msglen < msg_offset) { /* Read in the whole header */ if (MsgRead(rcvid, &header, msg_offset, 0) == -1) { status = errno; MsgError( rcvid, status); return Pt_HALT; /* bail out */ } log = &header; } log_msglen = msg_offset+log->msg_len; /* See if the whole message is in the buffer */ if (msglen < log_msglen) { struct log_message *log_msg = (struct log_message *)alloca(log_msglen); /* Read the remainder of the message into space on the stack */ if (log_msg == NULL || MsgRead( rcvid, log_msg, log_msglen, 0) == -1) { status = errno; MsgError( rcvid, status); return Pt_HALT; /* bail out */ } log = log_msg; } add_msg(text, log); status = 0; MspReply( rcvid, 0, 0, 0); } return Pt_HALT; }

 

 

应用注册了一个 input_proc() 函数来处理其他进程传递的非 Photon 消息。

 

input_proc() 函数首先检查传入消息类型。如果消息类型不能识别,则 input handler 函数立即返回,这样就能交由别的 input handler 正常处理该消息。

如果消息类型是一个 log 消息,函数首先确保 Photon 读取了缓冲区中的所有内容 ( 可以通过查看 msglen 参数判断 ) ,然后 input_proc() 函数调用 add_msg() 来添加 text widget 的消息处理来对该消息处理。

当消息处理完成时,返回 Pt_HALT ,这样确保消息不从队列中删除。

你可能感兴趣的:(QNX)