【操作系统】进程间通信 — 消息队列

15 篇文章 0 订阅
11 篇文章 1 订阅

Lab Week 08 实验报告

实验内容:进程间通信 — 消息机制

  • 编译运行课件 Lecture 09 示例代码:alg.9-1 ~ alg.9-2,指出你认为不合适的地方并加以改进。
  • 修改代码 alg.9-1 ~ alg.9-2,让 msgsnd 和 msgrcv 在两个终端并发随机执行,用第三个终端观察消息队列的变化情况。你认为会不会出现访问冲突?
  • 仿照 alg.9-1 ~ alg.9-2 的程序结构,编制基于 POSIX API 的进程间消息发送和消息接收例程。

I.Message Passing

1.相关函数

【函数说明参考 man7 Linux manual pages: section 3 的函数Reference】

  1. msgget() (头文件<sys/msg.h>)

    函数原型:int msgget(key_t __key, int __msgflg)

    函数功能: 根据传入的key参数,建立消息队列,并返回队列ID,如果key对应的队列已经创建,则直接返回队列ID,参数__msgflg可 用来执行以下操作:

     			**IPC_CREAT :** 如果内核中不存在键值与key相等,则新建消息队列,否则返回此消息队列的标识符;
    
     			**IPC_CREAT|IPC_EXCL:** 如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列					则报错
    
  2. msgctl() (头文件<sys/msg.h>)

    函数原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf)

    函数功能: 获取和设置消息队列的属性,其中,msqid为消息队列标识符,cmd可用来执行以下操作:

     			**IPC_STAT:** 取出此队列的msqid_ds结构体,并将它存放在buf指向的结构中
    
     			**IPC_SET:** 设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、							       	msg_perm.gid、msg_perm.mode以及msg_qbytes(详情见下文)
    
     			参数buf为消息队列管理结构体,即 ``struct msqid_ds``
    

    返回值: 成功返回0,失败返回-1

  3. msgsnd() (头文件<sys/msg.h>)

    函数原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)

    函数功能: 将参数msgp消息写入到标识符为msqid的消息队列,其中,msgp可以是任何类型的结构体,但第一个字段必须为long类 型,以表明此发送消息的类型,msgrcv()根据此接收消息;参数msgsz为要发送消息的大小;

     			参数msgflg对应以下操作:
    
     			**0:** 当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列
    
     			**IPC_NOWAIT:** 当消息队列已满的时候,msgsnd函数不等待立即返回
    
     			**IPC_NOERROR:** 若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。
    

    返回值: 成功返回0,失败返回-1

  4. msgrcv() (头文件<sys/msg.h>)

    函数原型:int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg)

    函数功能: 从标识符为msqid中的消息队列中读取消息,读取之后将该消息从消息队列中删除

    返回值: 成功返回0,失败返回-1

struct msqid_ds 结构体

每个消息队列都有一个msqid_ds结构体与其相关联。此结构体定义了消息队列的当前状态,结构体定义如下:

struct msqid_ds {
    struct ipc_perm msg_perm;     /* Ownership and permissions */
    time_t          msg_stime;    /* Time of last msgsnd(2) */
    time_t          msg_rtime;    /* Time of last msgrcv(2) */
    time_t          msg_ctime;    /* Time of last change */
    unsigned long   __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */
    msgqnum_t       msg_qnum;     /* Current number of messages in queue */
    msglen_t        msg_qbytes;   /* Maximum number of bytes allowed in queue */
    pid_t           msg_lspid;    /* PID of last msgsnd(2) */
    pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
};

输入命令 ipcs -l 查看Linux内核对消息队列空间的限制

image-20220327153404908

其中,系统最多的消息队列数量为32000,每条消息最大的字节数为8192Bytes,默认的单个队列大小为16384Bytes

1. 编译运行课件 Lecture 09 示例代码:alg.9-1 ~ alg.9-2,指出你认为不合适的地方并加以改进。

在以下例程代码中,通过调用Linux message-passing API,说明了在Linux中消息队列相关的API的使用方法:

alg.9-0-msgdata.h

#define TEXT_SIZE 512

/* message structure */
struct msg_struct {
    long int msg_type; /* 4 bytes reserved */
    char mtext[TEXT_SIZE]; /* unstructured binary data */
};

#define PERM S_IRUSR|S_IWUSR|IPC_CREAT

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

代码说明: 代码中定义了消息队列中每条消息的结构,包含消息的类型(用long int值来表示)和消息存储结构,其中每条消息的大小为512Bytes,基于Linux默认的消息队列大小(16384Bytes),在队列中总共可以驻留16384/512 = 32(条)消息。

alg.9-1-msgsnd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
 
#include "alg.9-0-msgdata.h"

int main(int argc, char *argv[])
{
    char pathname[80];
    struct stat fileattr;
    key_t key;
    struct msg_struct msgdata;
    long int msg_type;
    char buffer[TEXT_SIZE];
    int msqid, ret, count = 0;
    FILE *fp;

    if(argc < 2) {
        printf("Usage: ./a.out pathname\n");
        return EXIT_FAILURE;
    }
    strcpy(pathname, argv[1]);

    if(stat(pathname, &fileattr) == -1) {
        ret = creat(pathname, O_RDWR);
        if (ret == -1) {
            ERR_EXIT("creat()");
        }
        printf("shared file object created\n");
    }
    
    key = ftok(pathname, 0x27); /* proj_id can be any nonzero integer */
    if(key < 0) {
        ERR_EXIT("ftok()");
    }
	
    printf("\nIPC key = 0x%x\n", key);	
    
    msqid = msgget((key_t)key, 0666 | IPC_CREAT);
    if(msqid == -1) {
        ERR_EXIT("msgget()");
    }
 
    fp = fopen("./alg.9-0-msgsnd.txt", "rb");
    if(!fp) {
        ERR_EXIT("source data file: ./msgsnd.txt fopen()");
    }

    struct msqid_ds msqattr;
    ret = msgctl(msqid, IPC_STAT, &msqattr);
    printf("number of messages remainded = %ld, empty slots = %ld\n", msqattr.msg_qnum, 16384/TEXT_SIZE-msqattr.msg_qnum);
    printf("Blocking Sending ... \n");
    while (!feof(fp)) {
        ret = fscanf(fp, "%ld %s", &msg_type, buffer);
        if(ret == EOF) {
            break;
        }
        printf("%ld %s\n", msg_type, buffer);
                        
        msgdata.msg_type = msg_type;
        strcpy(msgdata.mtext, buffer);

        ret = msgsnd(msqid, (void *)&msgdata, TEXT_SIZE, 0); 
            /* msgflg 0 : blocking send, waiting when msg queue is full
               msgflg | IPC_NOWAIT : non-blocking send 
               TEXT_SIZE not including the (long int) msg_type */
        if(ret == -1) {
            ERR_EXIT("msgsnd()");
        }
        count++;
    }
    

    printf("number of sent messages = %d\n", count);

    fclose(fp);
    system("ipcs -q");
    exit(EXIT_SUCCESS);
}

代码说明:

在代码中打开了文件alg.9-0-msgsnd.txt 内容如下,结构为数据类型+人名

image-20220327163941173

首先利用ftok() 函数将pathname结合0x27生成IPC key,之后利用该key调用msgget()函数创建一个可读写的消息队列,并返回标识符,随后创建一个struct msqid_ds类型的结构体,调用msgctl()函数将消息队列关联的msqid_ds结构体载入到msqattr中,以便获得与该消息队列相关的数据,代码中打印的是消息队列中的消息数,即 printf("number of messages remainded = %ld, empty slots = %ld\n", msqattr.msg_qnum, 16384/TEXT_SIZE-msqattr.msg_qnum)

之后将打开的.txt文件中的内容载入buffer中,利用msgsnd()函数将消息传入到msgid指定的消息队列中,其中msgflg为0,表示blocking send,若消息队列满,msgsnd将会阻塞,直到消息能写进消息队列。

运行结果:

image-20220402121730634

number of messages remainded = 0, empty slots = 32来看,此时创建的消息队列中共有32个

alg.9-2-msgrcv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>

#include "alg.9-0-msgdata.h" 

int main(int argc, char *argv[]) /* Usage: ./b.out pathname msg_type */
{
    key_t key;
    struct stat fileattr;
    char pathname[80];
    int msqid, ret, count = 0;
    struct msg_struct msgdata;
    long int msgtype = 0;   /* 0 - type of any messages */

    if(argc < 2) {
        printf("Usage: ./b.out pathname msg_type\n");
        return EXIT_FAILURE;
    }
    strcpy(pathname, argv[1]);
    if(stat(pathname, &fileattr) == -1) {
        ERR_EXIT("shared file object stat error");
    }
    if((key = ftok(pathname, 0x27)) < 0) {
        ERR_EXIT("ftok()");
    }
    printf("\nIPC key = 0x%x\n", key);

    msqid = msgget((key_t)key, 0666);
        /* do not create any new msg queue
           ref to that for sending: msqid = msgget((key_t)key, 0666 | IPC_CREAT); */
    if(msqid == -1) {
        ERR_EXIT("msgget()");
    }
 
    if(argc < 3)
        msgtype = 0;
    else {
        msgtype = atol(argv[2]);
        if (msgtype < 0)
            msgtype = 0;
    }    /* determin msgtype (class number) */
    printf("Selected message type = %ld\n", msgtype);

    while (1) {
        ret = msgrcv(msqid, (void *)&msgdata, TEXT_SIZE, msgtype, IPC_NOWAIT); /* Non_blocking receive */
        if(ret == -1) { /* end of this msgtype */
            printf("number of received messages = %d\n", count);
            break;
        }
        
        printf("%ld %s\n", msgdata.msg_type, msgdata.mtext);
        count++;
    }
    
    struct msqid_ds msqattr;
    ret = msgctl(msqid, IPC_STAT, &msqattr);
    printf("number of messages remainding = %ld\n", msqattr.msg_qnum); 

    if(msqattr.msg_qnum == 0) {
        printf("do you want to delete this msg queue?(y/n)");
        if(getchar() == 'y') {
            if(msgctl(msqid, IPC_RMID, 0) == -1)
                perror("msgctl(IPC_RMID)");
        }
    }
   
    system("ipcs -q");
    exit(EXIT_SUCCESS);
}

代码说明:

这部分代码的功能是接收消息队列中的消息,要求在命令行中输入消息队列所在的路径与所取消息的消息类型。

首先根据路径名生成IPC Key,msqid = msgget((key_t)key, 0666) 获得消息队列的文件描述符msqid,

ret = msgrcv(msqid, (void *)&msgdata, TEXT_SIZE, msgtype, IPC_NOWAIT) 从上述得到的msqid对应的消息队列中取出消息,并指定消息类型msgtype,即命令行中输入的参数,进行非阻塞接收,即直接从消息队列中获取消息,若不存在则返回错误,不等待,这部分由一个while循环完成,直到无该消息类型的信息,才退出循环。

运行结果:

image-20220402121751799

指定从消息队列中接受消息的类型为1后,接受到了姓名为Luffy,Zoro,Sanji的消息,此时消息队列中还剩7个消息。

image-20220402121904268

再次从消息队列接受消息,这次接受消息类型为2的消息,得到Nami和Usopo,此时消息队列中还剩5个消息,这表明消息一旦从消息队列中取出,便不会再放回消息队列中。

image-20220402122332959

当选择接受消息的类型为0后,一次性将所有在消息队列中的信息取出(这里消息类型为0对应的是消息队列中的第一个消息,由于msgrcv() 在while循环中每次都以0作为接受消息的类型,所以会一次性将所有消息取出)并且提示是否移除消息队列,选择y,最后通过命令ipcs -q 系统中不存在消息队列,成功移除。

代码改进:

alg.9-1-msgsnd.c 中, 例程代码以msqid_ds结构体获取当前消息队列的信息,而这是在发送消息之前进行获取的,并不能实时地对消息队列是否满,当前队列的消息数等等的信息进行监测,因此需要将ret = msgctl(msqid, IPC_STAT, &msqattr) 放到while循环体中,以便每次发送一条消息后,都能够看到消息队列的存储情况。

此外,示例代码中提供的发送信息并不足以导致消息队列满,为了测试队满情况,第二轮测试将提升发送的消息的数量。

struct msqid_ds msqattr;
   // ret = msgctl(msqid, IPC_STAT, &msqattr);
    printf("Blocking Sending ... \n");
    while (!feof(fp)) {
       if(16384/TEXT_SIZE-msqattr.msg_qnum == 0){
            printf("Message Queue is full, blocking....\n");
        }
        ret = fscanf(fp, "%ld %s", &msg_type, buffer);
        
        if(ret == EOF) {
            break;
        }
        printf("%ld %s\n", msg_type, buffer);
                        
        msgdata.msg_type = msg_type;
        strcpy(msgdata.mtext, buffer);

        ret = msgsnd(msqid, (void *)&msgdata, TEXT_SIZE, 0); 
            /* msgflg 0 : blocking send, waiting when msg queue is full
               msgflg | IPC_NOWAIT : non-blocking send  
               TEXT_SIZE not including the (long int) msg_type */
        if(ret == -1) {
            ERR_EXIT("msgsnd()");
        }
        count++;
        ret = msgctl(msqid, IPC_STAT, &msqattr); //将msgctl放在循环体内,便于实时获取消息队列的信息
        printf("number of messages remainded = %ld\n", msqattr.msg_qnum);
       }

代码说明:

这部分是修改后的代码(这里发送端采用Blocking Send),主要针对消息队列的队满判断以及新增了打印出当前消息队列中消息个数的信息。

运行结果:

发送端:

image-20220405100848046

从运行结果来看,每次发送都打印出了当前消息队列中的消息数。

接收端:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tmh2CMKt-1649230090441)(/api/users/image?path=11232/images/1649227243007.png)]

成功地接收到所有信息。

现在对发送的消息数进行提升,由于在当前情况下,消息队列最多存储32条消息,现在将消息数增加到33条,查看队满情况:

image-20220405103721970

image-20220405104219756

在发送端发送第32条消息后,消息队列满,出现blocking,等待接收端的接收,第33条消息等待发送,而此时接收端还未进行接收。

image-20220405104402169

在接收端接收所有消息后,前32条消息一并被接收,此后第33条消息成功发送,并且被接收端接收。

2.让 msgsnd 和 msgrcv 在两个终端并发随机执行,用第三个终端观察消息队列的变化情况。你认为会不会出现访问冲突?

为了让消息队列发送方和接收方在两个终端并发随机执行,发送端每次发送都要进行2s休眠,接收端每次接收都要进行3s休眠,第三个终端中每3s打印出一次消息队列的信息,便于观察。直到发送端将所有数据发送完毕后,接收端接收完队列中的信息后,进程终止。

在发送端和接收端都进行休眠,且写入早于读出的情况下,消息队列不会出现访问冲突,因为在并发过程中,发送端早于接收端执行,并且接收端每次接收都能够从消息队列中获取消息,直到消息队列为空,故不会出现两个读写进程冲突的情况。

反之,如果发送端进行休眠,接收端不进行休眠,这样就会出现接收端在消息队列中接收不到信息的情况。

snd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "alg.9-0-msgdata.h"
int main(int argc, char *argv[])
{
    char pathname[80];
    struct stat fileattr;
    key_t key;
    struct msg_struct msgdata;
    long int msg_type;
    char buffer[TEXT_SIZE];
    int msqid, ret, count = 0;
    FILE *fp;
    if(argc < 2) {
        printf("Usage: ./a.out pathname\n");
        return EXIT_FAILURE;
    }
    strcpy(pathname, argv[1]);

    if(stat(pathname, &fileattr) == -1) {
        ret = creat(pathname, O_RDWR);
        if (ret == -1) {
            ERR_EXIT("creat()");
        }
        printf("shared file object created\n");
    }
    
    key = ftok(pathname, 0x27); /* proj_id can be any nonzero integer */
    if(key < 0) {
        ERR_EXIT("ftok()");
    }
    printf("\nIPC key = 0x%x\n", key);	
    
    msqid = msgget((key_t)key, 0666 | IPC_CREAT);
    if(msqid == -1) {
        ERR_EXIT("msgget()");
    }
 
    fp = fopen("./alg.9-0-msgsnd.txt", "rb");
    if(!fp) {
        ERR_EXIT("source data file: ./msgsnd.txt fopen()");
    }

    struct msqid_ds msqattr;
    ret = msgctl(msqid, IPC_STAT, &msqattr);
    printf("number of messages remainded = %ld, empty slots = %ld\n", msqattr.msg_qnum, 16384/TEXT_SIZE-msqattr.msg_qnum);
    printf("Blocking Sending ... \n");
    while (!feof(fp)) {
        ret = fscanf(fp, "%ld %s", &msg_type, buffer);
        if(ret == EOF) {
            break;
        }
        printf("sending: %s\n" ,buffer);
                        
        msgdata.msg_type = msg_type;
        strcpy(msgdata.mtext, buffer);
      
        ret = msgsnd(msqid, (void *)&msgdata, TEXT_SIZE, 0); 
        sleep(2);//发送端进行休眠
      
        if(ret == -1) {
            ERR_EXIT("msgsnd()");
        }
        count++;
    }
  

    printf("number of sent messages = %d\n", count);

    fclose(fp);
    exit(EXIT_SUCCESS);
}

代码说明:

在执行完 msgsnd()消息队列发送操作后,进行sleep(2) 两秒的休眠,避免并发冲突。

rev.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>

#include "alg.9-0-msgdata.h" 

int main(int argc, char *argv[]) /* Usage: ./b.out pathname msg_type */
{
    key_t key;
    struct stat fileattr;
    char pathname[80];
    int msqid, ret, count = 0;
    struct msg_struct msgdata;
    long int msgtype = 0;   /* 0 - type of any messages */

    if(argc < 2) {
        printf("Usage: ./b.out pathname msg_type\n");
        return EXIT_FAILURE;
    }
    strcpy(pathname, argv[1]);
    if(stat(pathname, &fileattr) == -1) {
        ERR_EXIT("shared file object stat error");
    }
    if((key = ftok(pathname, 0x27)) < 0) {
        ERR_EXIT("ftok()");
    }
    printf("\nIPC key = 0x%x\n", key);

    msqid = msgget((key_t)key, 0666);
        /* do not create any new msg queue
           ref to that for sending: msqid = msgget((key_t)key, 0666 | IPC_CREAT); */
    if(msqid == -1) {
        ERR_EXIT("msgget()");
    }
    
    if(argc < 3)
        msgtype = 0;
    else {
        msgtype = atol(argv[2]);
        if (msgtype < 0)
            msgtype = 0;
    }    /* determin msgtype (class number) */
    printf("Selected message type = %ld\n", msgtype);


    while (1) {
        ret = msgrcv(msqid, (void *)&msgdata, TEXT_SIZE, msgtype, IPC_NOWAIT); /* Non_blocking receive */
        if(ret == -1) { /* end of this msgtype */
            printf("number of received messages = %d\n", count);
            break;
        }
        printf("Received: %s\n",msgdata.mtext);
        count++;
        sleep(3);
    }
    
    struct msqid_ds msqattr;
    ret = msgctl(msqid, IPC_STAT, &msqattr);
    if(msqattr.msg_qnum == 0) {
        printf("do you want to delete this msg queue?(y/n)");
        if(getchar() == 'y') {
            if(msgctl(msqid, IPC_RMID, 0) == -1)
                perror("msgctl(IPC_RMID)");
        }
    }
   
    exit(EXIT_SUCCESS);
}

代码说明:

这部分代码主要用于接收消息队列中的信息,每接收一次休眠3s,避免出现消息队列中无消息可读的情况。

cmd.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>

#include "alg.9-0-msgdata.h" 

int main(int argc, char *argv[]){
    struct msqid_ds msqattr;
    int ret,msqid;
    key_t key;
    struct stat fileattr;
    char pathname[80];

    if(argc < 2) {
        printf("Usage: ./a.out pathname\n");
        return EXIT_FAILURE;
    }
    strcpy(pathname, argv[1]);
    if(stat(pathname, &fileattr) == -1) {
        ERR_EXIT("shared file object stat error");
    }
    if((key = ftok(pathname, 0x27)) < 0) {
        ERR_EXIT("ftok()");
    }

    msqid = msgget((key_t)key, 0666);
    if(msqid == -1) {
        ERR_EXIT("msgget()");
    }
    printf("\nIPC key = 0x%x\n", key);	

    while(1){
        ret = msgctl(msqid, IPC_STAT, &msqattr);
        printf("number of messages remainded = %ld, empty slots = %ld\n", msqattr.msg_qnum, 16384/TEXT_SIZE-msqattr.msg_qnum);
        printf("---------------------------------------------\n");
        sleep(3);
        if(msqattr.msg_qnum == 0) break;
    }
    exit(EXIT_SUCCESS);
}

代码说明:

这部分代码主要用于显示消息队列中消息数与剩余的空间,通过在while循环中不断获取消息队列msqid_ds结构体的信息,即 ret = msgctl(msqid, IPC_STAT, &msqattr) 可以实现与当前消息队列中的状态同步,并且每打印一次信息休眠3s。

运行结果:

image-20220402174425365

由于发送端的速度比接收端要快,在发送端发送完所有信息后,接收端仍处于休眠状态,并准备接收消息队列中的信息,从第三个终端可以看到,此时消息队列中还存有4条信息等待接收。

image-20220402174452141

此时消息队列中的消息已被接收完毕,从第三个终端来看,消息队列从有4条信息等待接收,一直减少到0,最后对该消息队列进行移除。

3.编制基于 POSIX API 的进程间消息发送和消息接收例程

1.相关函数
  1. mq_open() (头文件<mqueue.h>)

    函数原型:mqd_t mq_open(const char *__name, int __oflag, ...)

    函数功能: 打开或创建一个消息队列。

     			name:表示消息队列的名字,它符合POSIX IPC的名字规则。				oflag:表示打开的方式,和open函数的类似。有必须的选项:O_RDONLY,O_WRONLY,O_RDWR,还有可选的选					项:O_NONBLOCK,O_CREAT,O_EXCL。
    
  2. mq_close() (头文件<mqueue.h>)

    函数原型:int mq_close(mqd_t __mqdes)

    函数功能: 用于关闭一个消息队列,关闭后,消息队列并不从系统中删除。

  3. mq_unlink() (头文件<mqueue.h>)

    函数原型:int mq_unlink(const char *__name)

    函数功能: 删除一个消息队列。

  4. mq_send() (头文件<mqueue.h>)

    函数原型:int mq_send(mqd_t __mqdes, const char *__msg_ptr, size_t __msg_len, unsigned int __msg_prio)

    函数功能: 发送消息至消息队列。其中,mqdes为消息队列描述符,msg_ptr为指向消息的指针,msg_len:消息长度, msg_prio:消 息优先级。

  5. mq_receive() (头文件<mqueue.h>)

    函数原型:ssize_t mq_receive(mqd_t __mqdes, char *__msg_ptr, size_t __msg_len, unsigned int *__msg_prio)

    函数功能: 删除一个消息队列。其中,mqdes为消息队列描述符,msg_ptr为返回接收到的消息指针,msg_len:消息长度, msg_prio:返回接收到的消息优先级

    需要注意的是,__msg_len 参数需要指定为mq_attr结构体中的mq_msgsize(根据man7: The msg_len argument must be greater than or equal to the mq_msgsize attribute of the queue(see mq_getattr(3)) )否则就会报错:message too long。

  6. mq_set_attr() (头文件<mqueue.h>)

    函数原型:mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr)

    函数功能: 设置消息队列属性

  7. mq_getattr() (头文件<mqueue.h>)

    函数原型:mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *attr)

    函数功能: 获取消息队列属性,返回结构体mq_attr到attr

    以下为结构体 mq_attr的定义:

    struct mq_attr {               long mq_flags;       /* Flags: 0 or O_NONBLOCK */               long mq_maxmsg;      /* Max. # of messages on queue */               long mq_msgsize;     /* Max. message size (bytes) */               long mq_curmsgs;     /* # of messages currently in queue */           };
    

    补充:

    • 与POSIX实现的共享内存类似,消息队列的文件储存于路径:/dev/mqueue/
    • mq_open() 中的pathname需要以/ 开头,如 /myshm
2.实现代码:

通过在两个终端中并发执行发送端和接收端,从而实现进程间消息发送和消息接收例程。

posix_snd.c

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/msg.h>#include <sys/stat.h>#include <fcntl.h>#include <mqueue.h>#include "alg.9-0-msgdata.h"int main(int argc, char *argv[]){    struct mq_attr mqAttr;        char pathname[80];    struct stat fileattr;    struct msg_struct msgdata;    long int msg_type;    char buffer[TEXT_SIZE];    int msqid, ret, count = 0;    mqd_t mqid;    if(argc < 2) {        printf("Usage: ./a.out pathname\n");        return EXIT_FAILURE;    }    strcpy(pathname, argv[1]);    mqid = mq_open(pathname, O_RDWR | O_CREAT, 0666, NULL);    printf("Message Queue created, message queue id: %d\n", mqid);    if(mqid == -1) {        ERR_EXIT("posix_snd:mq_open()");    }     printf("The existing message queue:\n");    system("ls -l /dev/mqueue/");    printf("Message sending...\n");    while(1){        printf("Enter message:\n");        fgets(buffer, TEXT_SIZE, stdin);        ret = mq_send(mqid, buffer, strlen(buffer) + 1, 0);        if(ret == -1) {            ERR_EXIT("mq_send()");        }        if(strncmp(buffer, "end", 3) == 0) { /* end of this message queue */            break;        }        printf("sent message: %s\n", buffer);                   count++;       }     if(mq_getattr(mqid, &mqAttr) < 0){        ERR_EXIT("posix_snd:mq_getattr()");    }    printf("Max of messages on queue : %ld, number of messages remainded: %ld\n", mqAttr.mq_msgsize, mqAttr.mq_curmsgs);    printf("number of sent messages = %d\n", count);    exit(EXIT_SUCCESS);    }

代码说明:

这部分为利用POSIX API实现的发送端,通过在终端中键入消息,即可传送到消息队列中。

具体实现为:通过 mq_open() 以传入的pathname创建一个消息队列,在while循环中,持续地调用 mq_send() 将从键盘中键入的一行消息发送到消息队列中,其中发送的消息优先级都为0,即不指定优先级;直到输入end,代表退出当前进程,以mq_getattr() 获取当前消息队列的信息,并打印出Max of messages on queue和number of messages remainded。

posix_rsv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <mqueue.h>

#include "alg.9-0-msgdata.h"

int main(int argc, char *argv[]){
    char pathname[80];
    int ret, count = 0;
    mqd_t mqid;
    char buffer[TEXT_SIZE];

    struct msg_struct msgdata;
    if(argc < 2) {
        printf("Usage: ./b.out pathname\n");
        return EXIT_FAILURE;
    }
    strcpy(pathname, argv[1]);

    mqid = mq_open(pathname, O_RDONLY, 0666, NULL);
    if(mqid == -1) {
        ERR_EXIT("mq_open()");
    }
 
    struct mq_attr mqAttr;
    printf("Waiting message...\n");
    while(1){
        if(mq_getattr(mqid, &mqAttr) < 0){
             ERR_EXIT("posix_rsv:mq_getattr()");
        }
        ret = mq_receive(mqid, buffer, mqAttr.mq_msgsize, NULL);
        if(strncmp(buffer, "end", 3) == 0) { /* end of this message queue */
            printf("number of received messages = %d\n", count);
            break;
        }
        printf("Received message: %s\n",buffer);
        count++;
   }

    printf("Max messqge: %ld, number of messages remainded: %ld\n", mqAttr.mq_maxmsg, mqAttr.mq_curmsgs);

    if(mq_close(mqid) == -1){
        ERR_EXIT("mq_close()");
    } 
    if(mqAttr.mq_curmsgs == 0){
        printf("do you want to delete this msg queue?(y/n)");
        if(getchar() == 'y') {
            if(mq_unlink(pathname))
                perror("mq_unlink()");
        }
        printf("Message has been removed.\n");
    }
    system("ls -l /dev/mqueue/");
    exit(EXIT_SUCCESS);
}

代码说明:

这部分为利用POSIX API实现的接收端,读取消息队列中的消息。

具体实现为:通过 mq_open() 以传入的pathname获取已经创建消息队列的描述符,在while循环中,持续地调用mq_getattr()mq_receive() 获取当前消息队列的信息长度并且将其作为后者的参数,其中接收的消息优先级都为0,即不指定优先级;直到接收到的消息为end,代表退出当前进程,最后调用mq_close() 关闭当前消息队列,再询问是否删除消息队列,若为是,则调用mq_unlink() 进行移除消息队列。

需要注意的是,当消息队列仍然存有消息而又要执行mq_unlink() 将会报错,在运行结果中将会进行说明。

运行结果:

初始阶段:

image-20220404104204542

当接收端和发送端都开始执行后,发送端等待用户键入消息,接收端等待消息队列中的消息,在路径/dev/mqueue/ 中显示存在文件shareq。

输入一条信息后:

image-20220404105617882

在接收端成功接收到发送端所输入消息: This is Message 1

输入四条信息后:

image-20220404105712615

在接收端成功接收到发送端所输入的共四条的消息。

结束写进程,在读进程中显示是否移除消息队列:

image-20220404105858312

路径/dev/mqueue/中 shareq文件已被移除。

II.总结

Linux在进程间通信的实现方面,提供了消息队列的接口,消息队列是存在于Linux内核中的消息链表,消息队列的生命周期与内核相同,每一个消息队列由一个独特的消息队列标识符来标识,允许进程间以消息块的形式进行沟通,在Linux提供的方法中,用户可以指定发送的消息类型,进而在接收时同样可以指定消息类型对接收的消息进行筛选,而在POSIX提供的接口当中,消息队列中的消息可以指定优先级,从而根据优先级对消息进行筛选。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值