一文搞懂消息队列

一.消息队列

1.1消息队列特点

Linux 中的消息队列具有以下特点:

  1. 基于内核:Linux 的消息队列是由内核提供的。它由内核维护和管理,可以在用户空间中进行操作和访问。

  2. 进程间通信:Linux 的消息队列是一种用于进程间通信的机制。它允许不同的进程之间通过发送和接收消息来实现数据的传递和共享。

  3. 高效性:Linux 的消息队列在内核中实现,因此具有较高的性能和效率。它是一种基于内存的通信方式,消息的传递速度较快。

  4. 异步通信:Linux 的消息队列支持异步通信。发送方可以将消息发送到消息队列中后立即返回,而不需要等待接收方的响应。

  5. 支持多种消息类型:Linux 的消息队列可以支持不同类型的消息。每个消息由消息类型和消息数据组成,接收方可以根据消息类型来进行区分和处理。

  6. 消息持久化:Linux 的消息队列可以通过设置相应的标志来实现消息的持久化。这意味着即使接收方在消息到达之前不可用,消息仍然会被保存在队列中,直到接收方准备好处理它。

需要注意的是,Linux 的消息队列仅仅是 IPC(Inter-Process Communication,进程间通信)机制之一,还有其他的方式,如管道、共享内存等。选择合适的通信机制需要根据具体的需求和应用场景进行评估。

补充

独立于进程
没有文件名和文件描述符
IPC对象具有key和ID

1.2编程步骤

使用消息队列进行编程通常包含以下步骤:

  1. 创建消息队列:首先,需要创建一个消息队列来存储和传递消息。具体的创建方式和语法取决于所用的编程语言和消息队列实现库。

  2. 发送消息:在发送消息之前,需要确定消息的格式和内容。然后,将消息发送到消息队列中,等待接收方来获取并处理该消息。

  3. 接收消息:接收方需要在消息队列中注册并等待接收消息。一旦有消息到达队列,接收方就可以从队列中获取消息并进行相应的处理。

  4. 处理消息:接收方获取到消息后,进行相应的处理逻辑。根据消息的内容和类型,可以进行业务逻辑的处理、数据操作等操作。

  5. 确认消息:在一些消息队列系统中,接收方需要确认已经成功接收和处理了消息。这样可以确保消息在发送和处理过程中的可靠性传递,避免丢失或重复处理。

  6. 销毁消息队列:当不再需要使用消息队列时,需要进行手动销毁或关闭消息队列,释放相关的资源。

需要注意的是,具体的编程步骤可能会因不同的编程语言、消息队列实现库和应用场景而有所差异。此外,还需了解所用消息队列的具体特性和相关的 API 文档,以便正确地使用和操作消息队列。

1.3函数接口

1.ftok()

ftok 函数是 POSIX 标准中定义的一个函数,用于将给定的路径名和项目标识符生成一个唯一的键值,通常用于创建和关联 System V 消息队列、共享内存和信号量等 IPC 机制。

ftok 函数原型如下:

key_t ftok(const char *pathname, int proj_id);
  • pathname:一个存在的文件的路径名。通常使用一个特定的文件作为路径名,以表明某个特定 IPC 对象和项目相关联。
  • proj_id:一个非零的整数,用于区分不同的项目。需要保证在同一个 pathname 下,不同的 proj_id 生成的键值是唯一的。

ftok 函数返回一个 key_t 类型的键值。该键值在后续的 msggetshmgetsemget 等函数中用于标识和关联IPC对象。

需要注意的是,ftok 函数的键值生成是基于文件的 i-node 号和 proj_id 进行计算的。因此,当使用 ftok 生成键值时,需要确保 pathname 所指向的文件是存在的且不会轻易改变,否则可能导致生成的键值发生变化。

以下是一个示例使用 ftok 函数的代码片段:

#include <sys/types.h>
#include <sys/ipc.h>

int main() {
    
    
    // 生成一个键值
    key_t key = ftok("/tmp/myfile", 'A');
    if (key == -1) {
    
    
        perror("ftok");
        return 1;
    }

    // 使用生成的键值进行其他操作,比如创建消息队列、共享内存、信号量等

    return 0;
}

在这个示例中,我们将路径名设为 “/tmp/myfile”,proj_id 设置为 ‘A’,并使用 ftok 生成一个键值,然后可以将这个键值传递给其他 IPC 相关的函数使用。

总之,ftok 函数用于将路径名和项目标识符转换为一个唯一的键值,用于标识和关联 System V 消息队列、共享内存和信号量等 IPC 机制。

2.msgget()

msgget 函数用于创建消息队列或获取已存在的消息队列的标识符。

函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

参数:

  • key:一个键值,通常使用 ftok 函数生成。该键值用于唯一标识一个消息队列。
  • msgflg:用于指定消息队列的权限和选项,可以通过按位或操作符 | 来设置多个选项。常用的选项包括:
    • IPC_CREAT:如果没有与给定 key 相关联的消息队列,则创建一个新的消息队列。
    • IPC_EXCL:与 IPC_CREAT 一起使用时,如果与给定 key 相关联的消息队列已经存在,则报错。

函数返回值:

  • 成功时,返回消息队列的标识符(非负整数)。
  • 失败时,返回 -1,并设置全局变量 errno 来指示错误原因。

以下是一个示例使用 msgget 的代码片段:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    
    
    key_t key = ftok("/tmp/myfile", 'A');  // 生成键值
    if (key == -1) {
    
    
        perror("ftok");
        return 1;
    }

    int msgflg = IPC_CREAT | 0666;  // 创建消息队列并设置权限

    // 创建或获取消息队列
    int msqid = msgget(key, msgflg);
    if (msqid == -1) {
    
    
        perror("msgget");
        return 1;
    }

    printf("成功创建或获取消息队列,标识符为: %d\n", msqid);

    return 0;
}

在这个示例中,我们调用 msgget 函数创建或获取一个消息队列,首先使用 ftok 生成键值,然后设置创建选项为 IPC_CREAT | 0666 来创建一个新的消息队列并设置权限。如果消息队列创建成功,msgget 函数将返回一个非负整数作为消息队列的标识符 msqid。如果创建失败,调用 perror 函数打印错误信息,并返回非零值。

需要注意的是,如果一个进程已经创建了一个与给定键值相关联的消息队列,其他进程可以通过相同的键值来获取该消息队列的标识符,从而实现进程间的通信。

总之,msgget 函数用于创建或获取消息队列标识符,用于进程间的消息通信。

3.msgsnd()

msgsnd 函数用于向消息队列发送消息。

函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数:

  • msqid:消息队列的标识符,由 msgget 函数返回。
  • msgp:指向要发送的消息的指针,通常是一个结构体指针,结构体定义应与消息队列中消息的结构匹配。
  • msgsz:要发送的消息的大小(以字节为单位)。
  • msgflg:发送消息的选项,可以通过按位或操作符 | 来设置多个选项,常用选项有:
    • IPC_NOWAIT:若消息队列已满,即时返回错误,而不是等待队列有空闲空间。
    • MSG_NOERROR:如果消息长度超过消息队列的最大字节数,则截断消息而不报错。

函数返回值:

  • 成功时,返回 0。
  • 失败时,返回 -1,并设置全局变量 errno 来指示错误原因。

以下是一个示例使用 msgsnd 的代码片段:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct message {
    
    
    long mtype;
    char mtext[100];
};

int main() {
    
    
    key_t key = ftok("/tmp/myfile", 'A');  // 生成键值
    if (key == -1) {
    
    
        perror("ftok");
        return 1;
    }

    int msgflg = IPC_CREAT | 0666;  // 创建消息队列并设置权限

    // 创建或获取消息队列
    int msqid = msgget(key, msgflg);
    if (msqid == -1) {
    
    
        perror("msgget");
        return 1;
    }

    // 准备要发送的消息
    struct message msg;
    msg.mtype = 1;  // 消息类型,可以根据需要自定义
    strncpy(msg.mtext, "Hello, message queue!", sizeof(msg.mtext));

    // 发送消息
    int result = msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
    if (result == -1) {
    
    
        perror("msgsnd");
        return 1;
    }

    printf("成功发送消息到消息队列\n");

    return 0;
}

在这个示例中,我们调用 msgsnd 函数向消息队列发送消息。首先使用 ftok 生成键值,然后创建或获取了一个消息队列,并将准备好的消息(定义为一个结构体)发送到消息队列中。如果发送成功,msgsnd 函数将返回 0;如果发送失败,返回值为 -1,并通过调用 perror 函数打印错误信息。

总之,msgsnd 函数用于向消息队列发送消息,需要指定消息队列的标识符、要发送的消息内容和选项。

4.msgrcv()

msgrcv 函数用于从消息队列接收消息。

函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数:

  • msqid:消息队列的标识符,由 msgget 函数返回。
  • msgp:指向接收消息的缓冲区的指针,通常是一个结构体指针,结构体定义应与消息队列中消息的结构匹配。
  • msgsz:接收缓冲区的大小(以字节为单位)。
  • msgtyp:指定要接收的消息类型。设置为 0 表示接收队列中的第一条消息,设置为正数表示接收指定类型的消息,设置为负数表示接收小于或等于指定类型的最小数值的消息。
  • msgflg:接收消息的选项,可以通过按位或操作符 | 来设置多个选项,常用选项有:
    • IPC_NOWAIT:若队列为空,即时返回错误,而不是等待队列有可用消息。

函数返回值:

  • 成功时,返回实际接收到的消息的长度(以字节为单位)。
  • 失败时,返回 -1,并设置全局变量 errno 来指示错误原因。

以下是一个示例使用 msgrcv 的代码片段:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct message {
    
    
    long mtype;
    char mtext[100];
};

int main() {
    
    
    key_t key = ftok("/tmp/myfile", 'A');  // 生成键值
    if (key == -1) {
    
    
        perror("ftok");
        return 1;
    }

    int msgflg = IPC_CREAT | 0666;  // 创建消息队列并设置权限

    // 创建或获取消息队列
    int msqid = msgget(key, msgflg);
    if (msqid == -1) {
    
    
        perror("msgget");
        return 1;
    }

    // 准备接收消息的缓冲区
    struct message received_msg;

    // 接收消息
    ssize_t result = msgrcv(msqid, &received_msg, sizeof(received_msg.mtext), 1, 0);
    if (result == -1) {
    
    
        perror("msgrcv");
        return 1;
    }

    printf("成功接收消息:%s\n", received_msg.mtext);

    return 0;
}

在这个示例中,我们调用 msgrcv 函数从消息队列接收消息。首先使用 ftok 生成键值,然后创建或获取一个消息队列,并准备一个缓冲区用于接收消息。通过调用 msgrcv 函数,指定要接收的消息类型为 1,然后将接收到的消息存储到接收缓冲区中。如果接收成功,msgrcv 函数将返回接收到的消息的长度(以字节为单位);如果接收失败,返回值为 -1,并通过调用 perror 函数打印错误信息。

总之,msgrcv 函数用于从消息队列中接收消息,需要指定消息队列的标识符、接收缓冲区的大小、要接收的消息类型和选项。

5.msgctl()
msgctl 函数用于控制消息队列,包括对消息队列的删除、获取与修改等操作。

函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数:

  • msqid:消息队列的标识符,由 msgget 函数返回。
  • cmd:控制操作的命令,可以是以下值之一:
    • IPC_RMID:私有的标识符被删除,该消息队列将被销毁。
    • IPC_SET:设置消息队列的属性,需要提供 buf 参数指向的 msqid_ds 结构体,用于存储需要修改的属性。
    • IPC_STAT:获取消息队列的属性,buf 参数指向的 msqid_ds 结构体将被填充为消息队列的当前属性值。
  • buf:指向 msqid_ds 结构体的指针,用于存储或获取消息队列的属性。

函数返回值:

  • 成功时,返回 0。
  • 失败时,返回 -1,并设置全局变量 errno 来指示错误原因。

以下是一个示例使用 msgctl 的代码片段:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    
    
    key_t key = ftok("/tmp/myfile", 'A');  // 生成键值
    if (key == -1) {
    
    
        perror("ftok");
        return 1;
    }

    int msgflg = IPC_CREAT | 0666;  // 创建消息队列并设置权限

    // 创建或获取消息队列
    int msqid = msgget(key, msgflg);
    if (msqid == -1) {
    
    
        perror("msgget");
        return 1;
    }

    // 删除消息队列
    int result = msgctl(msqid, IPC_RMID, NULL);
    if (result == -1) {
    
    
        perror("msgctl");
        return 1;
    }

    printf("成功删除消息队列\n");

    return 0;
}

在这个示例中,我们调用 msgctl 函数删除一个消息队列。首先使用 ftok 生成键值,然后创建或获取一个消息队列,并指定要删除操作的命令为 IPC_RMID,通过调用 msgctl 函数来删除消息队列。如果删除成功,msgctl 函数将返回 0;如果删除失败,返回值为 -1,并通过调用 perror 函数打印错误信息。

总之,msgctl 函数用于对消息队列进行控制操作,可以删除消息队列、获取消息队列属性或修改消息队列属性。需要提供消息队列的标识符、控制操作的命令以及可选的参数 buf 来存储或获取属性。

1.4编程实战

msgsend.c

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

struct msgsend
{
    
    
    long type; //第一个必须是long类型
    char text[1024];
};

int main(int argc, char const *argv[])
{
    
    
    //1.ftok产生唯一key值
    key_t key = ftok("/home/hq/demo/进程/a.txt", 'A');
    if (key < 0)
    {
    
    
        perror("key is err");
        return 0;
    }

    //2.创建或打开消息队列
    int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
    //等于0报错证明已经创建过相同key的消息队列
    if (msgid <= 0)
    {
    
    
        if (errno == 17)
        {
    
    
            msgid = msgget(key, 0666);
        }
        else
        {
    
    
            perror("msgget is err");
            exit(-1);
        }
    }

    //3.创建结构体(第一个必须是long类型)
    struct msgsend send;
    send.type = getpid();
    int ret;
    while (1)
    {
    
    
        //从终端输入
        fgets(send.text, sizeof(send.text), stdin);
        ret = msgsnd(msgid, &send, sizeof(send) - sizeof(long), 0);
        if (ret < 0)
        {
    
    
            perror("msgsend is err");
            exit(-1);
        }
        if(strcmp("quit\n",send.text)==0)
            exit(0);
    }
    return 0;
}

msgrcv.c

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

struct msgrcv
{
    
    
    long type; //第一个必须是long类型
    char text[1024];
};

int main(int argc, char const *argv[])
{
    
    
    //1.ftok产生唯一key值
    key_t key = ftok("/home/hq/demo/进程/a.txt", 'A');
    if (key < 0)
    {
    
    
        perror("key is err");
        return 0;
    }

    //2.创建或打开消息队列
    int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
    //等于0报错证明已经创建过相同key的消息队列
    if (msgid <= 0)
    {
    
    
        if (errno == 17)
        {
    
    
            msgid = msgget(key, 0666);
        }
        else
        {
    
    
            perror("msgget is err");
            exit(-1);
        }
    }

    struct msgrcv rcv;
    //rcv.type = getpid();
    int ret;
    while (1)
    {
    
    
        ret = msgrcv(msgid, &rcv, sizeof(rcv) - sizeof(long), 0, 0);
        if (ret < 0)
        {
    
    
            perror("msgrcv is err");
            exit(-1);
        }
        if (strcmp(rcv.text, "quit\n") == 0)
            break;
        printf("%s", rcv.text);
    }

    ret = msgctl(msgid, IPC_RMID, NULL);
    if (ret < 0)
    {
    
    
        perror("msgctl is err");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_73731708/article/details/133151724