4. QNX消息传递机制

任务目标

在QNX Neutrino系统上创建两个独立进程:客户端进程通过MsgSend()发送请求,服务端进程通过MsgReceive()接收并响应,实现一次完整的请求-响应通信,同时覆盖连接建立与错误处理路径。

环境与依赖

  • QNX SDP 7.0或更高版本,目标机为x86_64或ARM64架构
  • 编译工具链:qcc(QNX C编译器)
  • 两个源文件分别编译为独立可执行文件,运行于同一QNX节点

实现方案

服务端创建命名通道(pathname),客户端通过name_open()连接该通道。通信协议定义为:客户端发送一个整数请求,服务端返回该整数的平方值。错误处理覆盖:客户端连接失败、服务端接收失败、客户端发送失败三种路径。

完整代码

// server.c — 服务端进程
#include <stdio.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <process.h>

#define CHANNEL_NAME "square_svc"
#define REQ_TYPE 1

typedef struct {
    int type;
    int value;
} request_t;

typedef struct {
    int result;
} response_t;

int main(void) {
    int chid, rcvid;
    request_t req;
    response_t resp;

    chid = ChannelCreate(0);
    if (chid == -1) {
        perror("ChannelCreate");
        return 1;
    }

    if (name_attach(chid, CHANNEL_NAME, 0) == -1) {
        perror("name_attach");
        ChannelDestroy(chid);
        return 1;
    }

    printf("Server: channel '%s' ready (chid=%d)\n", CHANNEL_NAME, chid);

    while (1) {
        rcvid = MsgReceive(chid, &req, sizeof(req), NULL);
        if (rcvid == -1) {
            perror("MsgReceive");
            continue;
        }

        if (rcvid == 0) {
            // 脉冲消息(此处忽略)
            continue;
        }

        if (req.type != REQ_TYPE) {
            resp.result = -EINVAL;
        } else {
            resp.result = req.value * req.value;
        }

        if (MsgReply(rcvid, 0, &resp, sizeof(resp)) == -1) {
            perror("MsgReply");
        }
    }
    return 0;
}
// client.c — 客户端进程
#include <stdio.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <process.h>

#define CHANNEL_NAME "square_svc"
#define REQ_TYPE 1

typedef struct {
    int type;
    int value;
} request_t;

typedef struct {
    int result;
} response_t;

int main(int argc, char **argv) {
    int coid, status;
    int input = 5;
    request_t req;
    response_t resp;

    if (argc > 1) {
        input = atoi(argv[1]);
    }

    coid = name_open(CHANNEL_NAME, 0);
    if (coid == -1) {
        printf("Client: cannot connect to '%s' (errno=%d)\n",
               CHANNEL_NAME, errno);
        return 1;
    }

    req.type = REQ_TYPE;
    req.value = input;

    status = MsgSend(coid, &req, sizeof(req), &resp, sizeof(resp));
    if (status == -1) {
        printf("Client: MsgSend failed (errno=%d)\n", errno);
        name_close(coid);
        return 1;
    }

    if (resp.result < 0) {
        printf("Client: server returned error %d\n", -resp.result);
    } else {
        printf("Client: %d^2 = %d\n", input, resp.result);
    }

    name_close(coid);
    return 0;
}

编译与运行

# 在QNX主机环境编译
qcc -o server server.c
qcc -o client client.c

# 将两个可执行文件复制到目标机,然后在目标机shell中:
./server &
./client 7
./client 12

验证方法

  • 正常路径:启动服务端后运行./client 7,预期输出Client: 7^2 = 49;运行./client 12,预期输出Client: 12^2 = 144。
  • 连接失败路径:不启动服务端直接运行./client,预期输出包含cannot connect且errno为ENOENT(2),客户端退出码为1。
  • 服务端错误响应:修改客户端REQ_TYPE为其他值(如2)后重新编译运行,预期输出server returned error 22(EINVAL)。
  • 服务端接收验证:服务端控制台打印通道就绪信息;正常通信时服务端无额外输出,客户端打印计算结果即表示MsgReceive/MsgReply链路完整。

关键说明

  • name_attach()将通道ID与路径名绑定,客户端使用name_open()解析路径名并建立连接,返回连接ID(coid)。
  • MsgSend()为阻塞发送,服务端MsgReply()返回后客户端才继续执行,天然形成同步请求-响应语义。
  • 错误处理覆盖三类:name_open失败(服务端未启动)、MsgSend失败(服务端崩溃或通道关闭)、业务错误(通过响应结构体返回负的errno值)。
  • 服务端为无限循环,验证结束后在目标机用kill命令终止进程(如kill $(pidin | grep server | awk '{print $1}'))。