APUE编程:127---进程间通信(消息队列:struct  msqid_ds)

一、消息队列简介

  • 消息队列是消息的链接表,存储在内核中,由消息队列标识符标识

二、消息队列的系统限制

  • “导出的”:表示这种限制来源于其他限制(例如:在Linux中,最大消息数是根据最大队列数和队列中所允许的最大数据量决定的)
  • 最大队列数:还要根据系统上安装的RAM的数量决定
  • 队列的最大字节数限制:进一步限制了队列中将要存储的消息的最大长度

三、struct  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) */
};

四、打开/创建队列:msgget函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);

//返回值:成功返回消息队列ID;出错返回-1
  • 功能:用来打开一个现有消息队列或者创建一个新消息队列

参数:

初始化msqid_ds结构体:

如果msgget函数用来创建一个新消息队列,那么内核会自动把新消息队列的struct msqid_ds结构体做以下初始化:

  • 对ipc_perm结构体赋值(该结构体中的mode成员按msgget函数的msgflg参数中的相应权限位设置)
  • msg_qnum、msg_lspid、msg_lrpid、msg_stime、msg_rtime都设置为0
  • msg_ctime设置为当前时间
  • msg_qbytes设置为系统限制值

五、队列设置:msgctl函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

//返回值:成功返回0;失败返回-1
  • 功能:此函数可以对队列执行多种不同的操作
  • 此函数与信号量和共享存储函数(semctl、shmctl)一样,都是XSI  IPC中类似于ioctl的函数

cmd参数如下

  • IPC_STAT:取此队列的msqid_ds结构体,并将它存放在buf指向的结构中
  • IPC_SET:将字段msg_perm.uid、msg_perm.gid、msg_perm.mode、msg_qbytes从buf所指向的结构复制到与这个队列相关的msqid_ds结构中。(此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.uid或msg_perm.cuid。另一种是具有超级用户特权的进程。但只有超级用户才能增加msg_qbytes的值)
  • IPC_RMID:从系统中删除msqid参数指定的消息队列以及仍在该队列中的所有数据,这种删除立即生效。仍在使用这一消息队列的其它进程在它们下一次试图对此队列进行操作时,将得到EIDRM错误(此命令只能由下列两种进程执行:一种是其有效用户ID等于msg_perm.uid或msg_perm.cuid。另一种是具有超级用户特权的进程)
  • IPC_INFO(特定于Linux):返回buf指向的结构中有关系统范围消息队列限制和参数的信息。此结构属于msginfo类型(因此,需要强制转换)。如果定义了gnu_source feature test宏,则在<sys/msg.h>中定义(可以通过同名的/proc文件更改msgmni、msgmax和msgmnb设置;有关详细信息,请参阅proc(5))
struct msginfo {
    int msgpool; /* Size in kibibytes of buffer pool used to hold message data;unused within kernel */
    int msgmap;  /* Maximum number of entries in message map; unused within kernel */
    int msgmax;  /* Maximum number of bytes that can be written in a single message */
    int msgmnb;  /* Maximum number of bytes that can be written to queue; used to initialize msg_qbytes during queue creation (msgget(2)) */
    int msgmni;  /* Maximum number of message queues */
    int msgssz;  /* Message segment size;unused within kernel */
    int msgtql;  /* Maximum number of messages on all queues in system; unused within kernel */
    unsigned short int msgseg; /* Maximum number of segments; unused within kernel */
};
  • msg_info(特定于Linux):返回一个msginfo结构,该结构包含与ipc_info相同的信息,但返回的以下字段包含有关的信息消队列占用的系统资源:msgpool字段返回系统中当前存在的消息队列数;msgmap字段返回系统上所有队列中的消息总数;msgtql字段返回中所有消息的字节总数。系统上的所有队列

六、增加数据:msgsnd函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);

//返回值:成功返回0;失败返回-1
  • 功能:调用此函数将数据放到消息队列中
  • 当msgsnd成功返回时:消息队列相关的msqid_ds结构会随之更新,表明调用的进程ID(msg_lspid)、调用的时间(msg_stime)以及队列中新增的消息(msg_qnum)
  • 如果flag参数没有指定为IPC_NOWAIT,则进程会一直阻塞到:有空间可以容纳要发送的消息;或者从系统中删除此队列(返回EIDRM错误);或者捕捉到一个信号,并从信号处理程序返回(返回EINTR错误)

ptr参数:

  • 此参数指向一个长整型数,它包含了正的整型消息类型,其后紧接着的是消息数据若nbytes是0,则无消息数据
  • 此参数一般实际指向一个struct  msgbuf结构体的指针。mtype是一个正的长整型数,代表消息数据的类型(msgrcv可以根据消息类型来取出自己需要的哪种类型的数据)。mtext成员代表的是实际消息数据, DATA_SIZE大小是根据需要自己设定的(若nbytes是0,则无消息数据
  • 重点:在书写程序时,这个结构体需要自己声明定义,系统并不提供,并根据需要设定其中mtext成员的大小(因为数据的大小根据需要不同是不一样的)
struct msgbuf {  
    long mtype;            /* message type, must be > 0 */
    char mtext[DATA_SIZE]; /* message data */
};

nbytes参数:

  • 如果参数2为msgbuf结构体。则此参数对应于msgbuf结构体的mtext成员大小

flag参数:

  • 默认填0
  • IPC_NOWAIT:类似于文件IO的非阻塞IO标志。若消息队列已满(或是消息队列中的消息总数等于系统限制值,或消息队列中的字节总数等于系统限制值),则使msgsnd立即出错返回EAGAIN

七、获取消息:msgrcv函数

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type,int flag);

//返回值:若成功返回消息数据部分的长度;出错返回-1
  • 功能:此函数从队列中取用消息
  • 当msgrcv函数成功执行时:消息队列相关的msqid_ds结构会随之更新,表明调用的进程ID(msg_lspid)和调用时间(msg_rtime),并指示队列中的消息数减少了1个(msg_qnum)
  • 如果flag参数没有指定为IPC_NOWAIT,则进程会一直阻塞到有了指定类型的消息可用,或者从系统中删除了此消息队列(此时函数返回-1,errno设置为EINTR),或者捕捉到一个信号并从信号处理程序返回(此时函数返回-1,errno设置为EINTR)

ptr参数:

  • 和msgsnd一样,指向一个长整型数(其中存储的是返回的消息类型),其后跟随的是存储实际消息数据的缓冲区。也一般指向一个struct msgbuf结构体(见上面介绍)

nbytes参数:

  • 指定参数2的数据缓冲区的长度。若返回的消息长度大于nbytes,而且在flag中设置了MSG_NOERROR位,则该消息会被截断(在这种情况下,系统不会通知我们消息被截断了)。若没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍留在队列中)

type参数:

可以指定从消息队列中取出哪一种类型的消息,值如下:

  • type==0:返回队列中的第一个消息
  • type>0:返回队列中消息类型为type的第一个消息
  • type<0:返回队列中消息类型值小于等于type绝对值的消息。如果这种消息有若干个,则取类型值最小的消息

type值为非0用于以非先进先出次序读消息。例如:若应用程序对消息赋予优先权,那么type就可以是优先权值。如果一个消息队列由多个客户进程和一个服务器进程使用。那么type字段可以用来包含客户进程的进程ID(只要进程ID可以存储在长整型中)

flag参数:

  • 默认填0
  • IPC_NOWAIT:使操作不阻塞。如果没有指定类型的消息可用,则msgrcv返回-1,errno设置为ENOMSG

八、消息队列与全双工管道的效率比较

八、消息队列的特点

特点一:

  • 如果进程创建了一个消息队列,在该队列中放入了几则消息,然后进程终止,但是该消息队列及其内容不会被删除。它们会一直留在系统中直至发生下列动作为止:由某个进程调用msgrcv或msgctl读消息或删除消息队列;或某个进程执行ipcrm命令删除消息队列;或正在自举的系统删除消息队列
  • 消息队列与管道相比:当最后一个引用管道的进程终止时,管道就完全被删除了
  • 消息队列与FIFO相比:在最后一个引用FIFO的进程终止时,虽然FIFO的名字仍保留在系统中,直至被显式地删除,但是留在FIFO中的数据已被删除

特点二:

  • 可以用非先进先出次序处理数据:使用msgsnd添加数据时,可以通过struct msgbuf结构体的mtype成员设置消息的类型。msgrcv函数取数据时可以根据函数的type参数取出指定类型的数据

九、演示案例

  • 实现消息队列的信息传递
  • 实现:server端发送SERVERTYPE类型的消息,client接收SERVERTYPE类型的消息。然后client发送CLIENTTYPE类型的消息,server接收CLIENTTYPE类型的消息
//common.h头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/types.h>

#define PATHNAME "."
#define PROJID   0x6666

#define CLIENTTYPE 0x1
#define SERVERTYPE 0x2

#define DATASIZE 1024

struct msgbuf{
    long mtype;
    char mtext[DATASIZE];
};

int createMsg();

int getQueue();

int sendMsg(int msgid,const void* ptr,long type,int flag);

size_t recvMsg(int msgid,void* ptr,size_t ptrsize,long type,int flag);

int destoryMsgQueue(int msgid);
//msgqueue.c文件
#include "common.h"

static int com_create_msg(const char* pathname,int projid,int msgflg)
{
    key_t key;
    int msgQueueId;
    if((key=ftok(pathname,projid))==-1){
        perror("ftok");
        exit(EXIT_FAILURE);
    }
    if((msgQueueId=msgget(key,msgflg))==-1){
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    return msgQueueId;
}


int createMsg()
{
    int msgflg=IPC_CREAT | IPC_EXCL | 0666;
    return com_create_msg(PATHNAME,PROJID,msgflg);
}

int getQueue()
{
    int msgflg=0;
    return com_create_msg(PATHNAME,PROJID,msgflg);
}


//发送数据
int sendMsg(int msgid,const void* ptr,long type,int flag)
{
    struct msgbuf buff;
    memset(&buff,'\0',sizeof(buff));
    buff.mtype=type;
    memcpy(buff.mtext,ptr,strlen(ptr));
    if(msgsnd(msgid,&buff,sizeof(buff.mtext),flag)==-1){
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }
    return 0;
}

//接受数据
size_t recvMsg(int msgid,void* ptr,size_t ptrsize,long type,int flag)
{
    struct msgbuf buff;
    memset(&buff,'\0',sizeof(buff));
    if(msgrcv(msgid,&buff,sizeof(buff.mtext),type,flag)<0){
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }
    memset(ptr,'\0',ptrsize);
    memcpy(ptr,buff.mtext,strlen(buff.mtext));
    return 0;
}


int destoryMsgQueue(int msgid)
{
    if(msgctl(msgid,IPC_RMID,NULL)==-1){
        perror("msgctl");
        exit(EXIT_FAILURE);
    }
    return 0;
}
//server.c文件
#include "common.h"

int main()
{
    int msgid=createMsg();
    char buff[DATASIZE];
    while(1)
    {
        printf("Enter:");
        fflush(stdout);
        scanf("%s",buff);
        if(strncmp(buff,"quit",4)==0)
        {
            sendMsg(msgid,buff,SERVERTYPE,0);
            /*为什么要goto:因为对方quit之后已经destoryMsgQueue过消息队列了,
            所以要goto到destoryMsgQueue函数之后*/
            goto end;
        }
        sendMsg(msgid,buff,SERVERTYPE,0);

        recvMsg(msgid,buff,sizeof(buff),CLIENTTYPE,0);
        if(strncmp(buff,"quit",4)==0)
            break;
        printf("recv:%s\n",buff);
    }

    destoryMsgQueue(msgid);
end:
    printf("end!\n");
    exit(0);
}
//client.c文件
#include "common.h"

int main()
{
    int msgid=getQueue();
    char buff[DATASIZE];

    while(1)
    {
        recvMsg(msgid,buff,sizeof(buff),SERVERTYPE,0);
        if(strncmp(buff,"quit",4)==0)
            break;
        printf("recv:%s\n",buff);

        printf("Enter:");
        fflush(stdout);
        scanf("%s",buff);
        if(strncmp(buff,"quit",4)==0)
        {
            sendMsg(msgid,buff,CLIENTTYPE,0);
            /*为什么要goto:因为对方quit之后已经destoryMsgQueue过消息队列了,
            所以要goto到destoryMsgQueue函数之后*/
            goto end;
        }
        sendMsg(msgid,buff,CLIENTTYPE,0);
    }

    destoryMsgQueue(msgid);
end:
    printf("end!\n");
    exit(0);
}

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值