linux应用 进程间通信之消息队列(System V)

1、定义

System V消息队列是传统的Linux消息队列机制,它使用一组系统调用来创建、发送和接收消息。它的特点是可以在不同进程之间共享消息队列,但是在使用时需要手动管理消息队列的创建和删除。

优点:

  • 可以实现异步通信:发送进程将消息放入消息队列后即可继续执行,不需要等待接收进程的响应,接收进程可以在合适的时候去读取消息。
  • 支持多对多通信:多个进程可以同时向同一个消息队列发送消息,多个进程也可以同时从同一个消息队列接收消息。
  • 可以实现进程解耦:发送进程和接收进程之间通过消息队列通信,不需要直接的共享内存或者使用管道等方式,从而实现了进程解耦。
  • 消息队列中的消息可以按照优先级进行处理,这样可以实现一些特殊的消息处理逻辑。

缺点:

  • 消息队列是基于内核的,因此涉及到用户态和内核态的切换,可能会引入一定的性能开销。
  • 消息队列的容量有限,当消息队列满了之后,发送进程将无法再发送消息,接收进程也无法再接收消息,可能会引发消息丢失的问题。
  • System V消息队列是Linux特有的IPC机制,因此在不同的操作系统上可能不具备可移植性。

2、常用接口介绍

2.1 编程常用接口和数据结构

2.1.1 ftok函数

ftok函数用于生成一个System V IPC对象(如消息队列、共享内存等)的key。它将pathname和proj_id组合起来,生成一个唯一的key,用于标识一个System V IPC对象。

key_t ftok(const char *pathname, int proj_id);
  • 入参:pathname是一个路径名,proj_id是一个用户指定的整数。
  • 返回值:返回一个基于pathname和proj_id生成的key。
2.2.2 msgget 函数

msgget函数用于创建一个新的消息队列或者获取一个已存在的消息队列。它接受一个key和一些标志作为参数。

int msgget(key_t key, int msgflg);
  • 入参:key是一个由ftok函数生成的key,msgflg是消息队列的权限标志。
  • 返回值:成功时返回消息队列的标识符(非负整数),失败时返回-1。
2.2.3 msgsnd 函数

msgsnd函数用于向指定的消息队列中发送消息。它接受消息队列的标识符、消息缓冲区的指针、消息的大小和发送标志作为参数。

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • 入参:msqid是消息队列的标识符,msgp是指向消息缓冲区的指针,msgsz是消息的大小,msgflg是消息发送的标志。
  • 返回值:成功时返回0,失败时返回-1。
2.2.4 msgrcv 函数

msgrcv函数用于从指定的消息队列中接收消息。它接受消息队列的标识符、消息缓冲区的指针、消息的大小、消息的类型和接收标志作为参数。

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  • 入参:msqid是消息队列的标识符,msgp是指向消息缓冲区的指针,msgsz是消息的大小,msgtyp是消息的类型,msgflg是消息接收的标志。
  • 返回值:成功时返回接收到的消息的大小,失败时返回-1。
2.2.5 msgctl 函数

msgctl函数用于控制消息队列,可以用来删除消息队列、获取消息队列的状态信息等。它接受消息队列的标识符、控制命令和消息队列数据结构的指针作为参数,根据控制命令的不同执行相应的操作。

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • 入参:msqid是消息队列的标识符,cmd是控制命令,buf是指向消息队列数据结构的指针。
  • 返回值:成功时返回0,失败时返回-1。

其中cmd取值如下:

  • IPC_STAT:获取消息队列的状态信息。
  • IPC_SET:设置消息队列的状态信息。
  • IPC_RMID:删除消息队列。

其中struct msqid_ds 定义如下:

struct msqid_ds {
    struct ipc_perm msg_perm;    /* 消息队列的权限信息 */
    time_t msg_stime;            /* 上次发送消息的时间 */
    time_t msg_rtime;            /* 上次接收消息的时间 */
    time_t msg_ctime;            /* 上次变更状态的时间 */
    unsigned long __msg_cbytes;  /* 队列中的字节数 */
    msgqnum_t msg_qnum;          /* 队列中的消息数 */
    msglen_t msg_qbytes;         /* 队列的最大字节数 */
    pid_t msg_lspid;             /* 最后发送消息的进程ID */
    pid_t msg_lrpid;             /* 最后接收消息的进程ID */
};
2.2.6 关于msgflg

IPC_CREAT

  • 作用:如果消息队列不存在,则创建一个新的消息队列。
  • 示例:msgget(key, IPC_CREAT | 0666)

IPC_EXCL

  • 作用:与IPC_CREAT一起使用时,如果消息队列已经存在,则返回错误。
  • 示例:msgget(key, IPC_CREAT | IPC_EXCL | 0666)

IPC_NOWAIT

  • 作用:在发送消息时,如果消息队列已满,则立即返回错误,而不是阻塞等待消息队列可用。
  • 示例:msgsnd(msqid, &msg, sizeof(msg), IPC_NOWAIT)

IPC_PRIVATE

  • 作用:用于创建一个唯一的消息队列标识符,通常不与msgget一起使用,而是作为msqid_ds结构体的msg_perm.__seq字段的默认值。
  • 示例:msgget(IPC_PRIVATE, 0666)

2.2 控制台常用命令

2.2.1 ipcs

ipcs命令用于显示系统中的IPC资源信息,包括消息队列、共享内存和信号量。-q选项表示只显示消息队列的信息:

ipcs -q
2.2.2 ipcrm

ipcrm -Q :pcrm命令用于删除IPC资源,包括消息队列、共享内存和信号量。-Q选项表示删除消息队列,是消息队列的标识符:

ipcrm -Q 12345

3、编程示例

测试代码如下:

#include 
#include 
#include 
#include 
#include 
#include 

#define MSG_SIZE 128
#define MSG_FILE_PATH "/home/msgq"

struct msg_buffer {
    long msg_type;
    char msg_text[MSG_SIZE];
};

int main(int argc, char *argv[]) 
{
    int msqid;
    struct msg_buffer message = {0};
    key_t key;
    
    // 文件不存在则创建文件
    if (-1 == access(MSG_FILE_PATH, F_OK))
    {
        system("touch "MSG_FILE_PATH);
    }

    // 获取key
    if((key = ftok(MSG_FILE_PATH, 'a')) < 0)
    {
        return 0;
    }

    // 命令行参数 
    // 第一个参数 0表示发送 1表示接收 2表示删除
    // 第二个参数 0表示测试一次 1表示while循环测试
    if (argc != 3) 
    {
        printf("Usage: %s 0|1|2", argv[0]);
        return 0;
    }

    if (!strcmp(argv[1], "0"))
    {
        do
        {
            // 发送消息
            msqid = msgget(key, 0666 | IPC_CREAT);
            message.msg_type = 1;
            strcpy(message.msg_text, "Hello, this is a message.");
            msgsnd(msqid, &message, sizeof(message.msg_text), 0);
            printf("^^^ send ^^^\r\n msqid = %d\r\n type:%ld \r\n data:%s\n", msqid, message.msg_type, message.msg_text);
            sleep(3);
        }
        while(atoi(argv[2]));

    } 
    else if (!strcmp(argv[1], "1")) 
    {
        // 接收消息
        do
        {
            msqid = msgget(key, 0666);
            msgrcv(msqid, &message, sizeof(message.msg_text), 1, 0);
            printf("^^^ received ^^^\r\n msqid = %d\r\n type:%ld \r\n data:%s\n", msqid, message.msg_type, message.msg_text);
        }
        while(atoi(argv[2]));

    } 
    else if (!strcmp(argv[1], "2")) 
    {
        // 删除消息
        msqid = msgget(key, 0666);
        msgctl(msqid, IPC_RMID, NULL);
        printf("^^^ delete ok ^^^\r\n");
    }
    else 
    {
        printf("Invalid command\n");
        return 1;
    }

    return 0;
}

根据执行程序命令行参数不同执行不同操作,第一个参数0表示发送,1表示读取,2表示删除,第二个参数0表示不用while循环,1表示启用循环,首先执行两次不启用while的发送操作:

linux应用 进程间通信之消息队列(System V)_第1张图片

再执行两次接收操作可以查看到消息已经被接收完毕:

linux应用 进程间通信之消息队列(System V)_第2张图片

执行删除操作可以看到消息队列被删除掉:

linux应用 进程间通信之消息队列(System V)_第3张图片

开启两个终端,启用while循环开启进程间通信的测试,可以看到发送端每3秒发送数据,接收端接收正常:

linux应用 进程间通信之消息队列(System V)_第4张图片

4、总结

本文阐述了进程间通信之消息队列(System V)的定义,列举了编程中使用的接口和linux命令,编写了测试用例测试相关功能。

你可能感兴趣的:(linux应用,linux,运维,服务器)