消息队列未按预期接收信息

Message queue not receiving information as expected

我正在编写一个程序,我想使用消息队列来安排程序结束。这是代码的架构:

typedef struct {
    long id;
    int num;
}Message;

int disconnect = 0;

void disconnection() {
    disconnect = 1;
}

int main () {
   
    //...

    int n = fork();
    int id_queue = msgget(ftok("connection.h", 66), 0600|IPC_CREAT);

    switch(n) {

        case 0: //child process

            Message message;
            message.id = 1;
            message.num = 0;

            while (1) {
                // ...

                msgrcv(id_queue, (struct msgbuf *) &message, sizeof(int), 1, IPC_NOWAIT);

                if (message.num == 1) break;
            }

            write(1, "Exit successfull\n", strlen("exit successfulll")); //The child process does not reach this line

            msgctl(id_queue, IPC_RMID, (struct msqid_ds *)NULL);
        break;

        default: //main process
            signal(SIGINT, disconnection);

            while (!disconnect) { /* ... */ }

            Message msg;
            msg.id = 1;
            msg.num = 1;

            msgsnd(id_queue, (struct msgbuf *) &msg, sizeof(int), IPC_NOWAIT);

            wait(NULL); //waiting for child process to end
        break;

}

如您所见,该程序一直运行到我用 SIGINT 中断它为止。为了结束子进程的断开连接过程是发送一条消息,该消息只是一个 int,如果它是 1,则子进程循环应该中断。我已经对其进行了测试,但它没有进入(在 while(1) 循环之后不打印 write)所以我发现我在通过消息队列发送或接收数据时遇到了一些错误。

怎么了?

此外,程序在我发送 SIGINT 中断后结束并且 wait(NULL) 没有等待子进程结束,因为 write 没有通过屏幕打印。 .

有很多问题。

当您执行 ctrl-c 时,信号将同时发送 parent 和 child。 child 必须 禁用 信号,否则它将被默认信号操作杀死。

parent 应该 在执行 msgsnd 时使用 IPC_NOWAIT

在child中,可以IPC_NOWAIT,但我们必须检查return值和 loop 如果它 returns -1(即消息内容 not 否则有效)。

您可以case[IIRC]中放置范围声明。最好将它们移动到功能范围。

disconnect 应声明为 volatile。这足以满足您的用例。在更一般的情况下[正如其他人提到的那样],你应该使用 stdatomic.h 原语。

有很多特殊常量的硬编码(例如 1 对应 .num)。

msgsndmsgrcv 的长度参数与 sizeof(int) 硬连线。 [如果将其他字段添加到 Message 结构],这不会很好地扩展。

可以使用ftok获得key_t值。但是,在这里,它太过分了。只需在 parent [fork 之前] 使用 IPC_PRIVATE。这就是大多数程序在 fork

时所做的事情

我重构了代码。我使用 cpp 条件标记了更改:

#if 0
// old code
#else
// new code
#endif

无论如何,这是工作代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define fault(_fmt...) \
    do { \
        printf(_fmt); \
        exit(1); \
    } while (0)

typedef struct {
    long id;
    int num;
} Message;

// equates to id
enum {
    MSGID_ANY,                      // receive any message
    MSGID_TOCLD,                    // send/receive messages to child
    MSGID_TOPAR,                    // send/receive messages to parent
};

// equates to num
enum {
    NUM_DO_X,                       // perform "x" ...
    NUM_DO_Y,                       // perform "y" ...
    NUM_STOP,                       // stop request
};

#if 0
int disconnect = 0;
#else
volatile int disconnect = 0;
#endif

void
disconnection()
{
    disconnect = 1;
}

int
main()
{
#if 1
    Message msg;
    int err;
#endif

    // ...

#if 0
    int n = fork();
    int id_queue = msgget(ftok("connection.h", 66), 0600 | IPC_CREAT);
#else
    int id_queue = msgget(IPC_PRIVATE, 0600 | IPC_CREAT);
    printf("parent: id_queue=%d\n",id_queue);
    int n = fork();
#endif

    switch (n) {
    case 0:  // child process
#if 0
        Message message;
#endif
        msg.id = MSGID_TOCLD;
        msg.num = 0;

#if 1
        signal(SIGINT, SIG_IGN);
#endif

        while (1) {
            // ...
#if 0
            msgrcv(id_queue, (struct msgbuf *) &msg, sizeof(int), 1,
                IPC_NOWAIT);
#else
            err = msgrcv(id_queue, (struct msgbuf *) &msg,
                sizeof(msg) - sizeof(msg.id), MSGID_TOCLD, IPC_NOWAIT);
            if (err < 0)
                continue;
            printf("child: got num=%d\n",msg.num);
#endif
            if (msg.num == NUM_STOP)
                break;
        }

        // The child process does not reach this line
#if 0
        write(1, "Exit successfull\n", strlen("exit successfulll"));
#else
        printf("Exit successful\n");
#endif

        msgctl(id_queue, IPC_RMID, (struct msqid_ds *) NULL);
        break;

    default:  // main process
        signal(SIGINT, disconnection);

        while (!disconnect) {
        }
        printf("Parent got signal\n");

#if 0
        Message msg;
#endif
        msg.id = MSGID_TOCLD;
        msg.num = NUM_STOP;

#if 0
        msgsnd(id_queue, (struct msgbuf *) &msg, sizeof(int), IPC_NOWAIT);
#else
        err = msgsnd(id_queue, (struct msgbuf *) &msg,
            sizeof(msg) - sizeof(msg.id), 0);
        if (err < 0)
            fault("parent: msgsnd fail -- %s\n",strerror(errno));
#endif

        // waiting for child process to end
        wait(NULL);
        break;
    }

    return 0;
}