IPC - Unix - 消息队列

1、消息队列

消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。与匿名管道不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。用户可以从消息队列中读取数据和添加消息,其中发送进程添加消息到队列的末尾,接收进程在队列的头部接收消息,消息一旦被接收,就会从队列中删除。和FIFO有点类似,但是它可以实现消息的随机查询,比FIFO具有更大的优势(比如按消息的类型字段取消息)。

2、msqid_ds

Linux内核中,每个消息队列都维护一个结构体msqid_ds,此结构体保存着消息队列当前的状态信息。该结构定义在头文件linux/msg.h中,具体如下:

struct msqid_ds
{
   struct_ipc_perm  msg_perm;   //是一个ipc_perm的结构,保存了消息队列的存取权限,以及队列的用户ID、组ID等信息
   struct_msg  *msg_first;   //指向队列中的第一条消息
   struct_msg  *msg_last;   //指向队列中的最后一条消息
   __kernel_t time_t  msg_stime;   //向消息队列发送最后一条信息的时间
   __kernel_t time_t  msg_rtime;   //从消息队列取最后一条信息的时间
   __kernel_t time_t  msg_ctime;  //最后一次变更消息队列的时间
   unsigned long  msg_lcbytes;
   unsigned long  msg_lqbytes;
   unsigned short  msg_cbytes;    //消息队列中所有消息占的字节数
   unsigned short  msg_qnum;     //消息队列中消息的数目
   unsigned short  msg_qbytes;   //消息队列的最大字节数
   __kernel_ipc_pid_t  msg_lspid;  //向消息队列发送最后一条消息的进程ID
   __kernel_ipc_pid_t  msg_lrpid;   //从消息队列读取最后一条信息的进程ID
};

struct msqid_ds
  {
    struct ipc_perm msg_perm;  //消息队列访问权限
    struct msg *msg_first;    //指向第一个消息的指针
    struct msg *msg_last;     //指向最后一个消息的指针
	ulong  msg_cbytes;       //消息队列当前的字节数
	ulong  msg_qnum;        //消息队列当前的消息个数
	ulong  msg_qbytes;     //消息队列可容纳的最大字节数
	pid_t  msg_lsqid;     //最后发送消息的进程号ID
	pid_t  msg_lrqid;     //最后接收消息的进程号ID
	time_t msg_stime;     //最后发送消息的时间
	time_t msg_rtime;     //最后接收消息的时间
	time_t msg_ctime;    //最近修改消息队列的时间
};

3、键值 - ftok

消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应惟一的键值。要获得一个消息队列的描述符,只需提供该消息队列的键值即可,该键值通常由函数ftok返回。

#include <sys/ipc.h>
key_t ftok(const char *pathname,int proj_id);

//封装ftok
key_t Ftok(const char *pathname,int proj_id)
{
  key_t key= ftok(pathname,proj_id);
  if(key== -1)
  {
    perror("ftok.");
    exit(1);
  }
  return key;
}

4、msgget函数原型

msgget(得到消息队列标识符或创建一个消息队列对象)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

函数说明

得到消息队列标识符或创建一个消息队列对象并返回消息队列标识符

函数原型

int msgget(key_t key, int msgflg)

函数传入值

key

0(IPC_PRIVATE):会建立新的消息队列

大于0的32位整数:视参数msgflg来确定操作。通常要求此值来源于ftok返回的IPC键值

msgflg

0:取消息队列标识符,若不存在则函数会报错

IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符

IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错

函数返回值

成功:返回消息队列的标识符

出错:-1,错误原因存于error中

附加说明

上述msgflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定消息队列的存取权限

错误代码

EACCES:指定的消息队列已存在,但调用进程没有权限访问它

EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志

ENOENT:key指定的消息队列不存在同时msgflg中没有指定IPC_CREAT标志

ENOMEM:需要建立消息队列,但内存不足

ENOSPC:需要建立消息队列,但已达到系统的限制

如果用msgget创建了一个新的消息队列对象时,则msqid_ds结构成员变量的值设置如下:

        msg_qnum、msg_lspid、msg_lrpid、 msg_stime、msg_rtime设置为0。

        msg_ctime设置为当前时间。

        msg_qbytes设成系统的限制值。

        msgflg的读写权限写入msg_perm.mode中。

        msg_perm结构的uid和cuid成员被设置成当前进程的有效用户ID,gid和cuid成员被设置成当前进程的有效组ID。

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

消息队列标识符

cmd

 

IPC_STAT:获得msgid的消息队列头数据到buf中

IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes

buf:消息队列管理结构体,请参见消息队列内核结构说明部分

函数返回值

成功:0

出错:-1,错误原因存于error中

错误代码

EACCESS:参数cmd为IPC_STAT,确无权限读取该消息队列

EFAULT:参数buf指向无效的内存地址

EIDRM:标识符为msqid的消息队列已被删除

EINVAL:无效的参数cmd或msqid

EPERM:参数cmd为IPC_SET或IPC_RMID,却无足够的权限执行

6、msgsnd函数原型

msgsnd (将消息写入到消息队列)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

函数说明

将msgp消息写入到标识符为msqid的消息队列

函数原型

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

函数传入值

msqid

消息队列标识符

msgp

发送给队列的消息。msgp可以是任何类型的结构体,但第一个字段必须为long类型,即表明此发送消息的类型,msgrcv根据此接收消息。msgp定义的参照格式如下:

    struct s_msg{ /*msgp定义的参照格式*/
     long type; /* 必须大于0,消息类型 */
     char mtext[256]; /*消息正文,可以是其他任何类型*/
    } msgp;

msgsz

要发送消息的大小,不含消息类型占用的4个字节,即mtext的长度

msgflg

0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列

IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回

IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。

函数返回值

成功:0

出错:-1,错误原因存于error中

错误代码

EAGAIN:参数msgflg设为IPC_NOWAIT,而消息队列已满

EIDRM:标识符为msqid的消息队列已被删除

EACCESS:无权限写入消息队列

EFAULT:参数msgp指向无效的内存地址

EINTR:队列已满而处于等待情况下被信号中断

EINVAL:无效的参数msqid、msgsz或参数消息类型type小于0

   msgsnd()为阻塞函数,当消息队列容量满或消息个数满会阻塞。消息队列已被删除,则返回EIDRM错误;被信号中断返回E_INTR错误。

 如果设置IPC_NOWAIT消息队列满或个数满时会返回-1,并且置EAGAIN错误。

msgsnd()解除阻塞的条件有以下三个条件:

①    不满足消息队列满或个数满两个条件,即消息队列中有容纳该消息的空间。

②    msqid代表的消息队列被删除。

③    调用msgsnd函数的进程被信号中断。

7、msgrcv函数原型

msgrcv (从消息队列读取消息)

所需头文件

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

函数说明

从标识符为msqid的消息队列读取消息并存于msgp中,读取后把此消息从消息队列中删除

函数原型

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

                      int msgflg);

函数传入值

msqid

消息队列标识符

msgp

存放消息的结构体,结构体类型要与msgsnd函数发送的类型相同

msgsz

要接收消息的大小,不含消息类型占用的4个字节

msgtyp

0:接收第一个消息

>0:接收类型等于msgtyp的第一个消息

<0:接收类型等于或者小于msgtyp绝对值的第一个消息

msgflg

0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待

IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG

IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息

IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃

函数返回值

成功:实际读取到的消息数据长度

出错:-1,错误原因存于error中

错误代码

E2BIG:消息数据长度大于msgsz而msgflag没有设置IPC_NOERROR

EIDRM:标识符为msqid的消息队列已被删除

EACCESS:无权限读取该消息队列

EFAULT:参数msgp指向无效的内存地址

ENOMSG:参数msgflg设为IPC_NOWAIT,而消息队列中无消息可读

EINTR:等待读取队列内的消息情况下被信号中断

msgrcv()解除阻塞的条件有以下三个:

①    消息队列中有了满足条件的消息。

②    msqid代表的消息队列被删除。

③    调用msgrcv()的进程被信号中断。

8、工作模式

9、示例

发送端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include  <time.h>

#define TEXT_SIZE  512

struct msgbuf
{
    long mtype ;
    int  status ;
    char time[20] ;
    char mtext[TEXT_SIZE] ;
}  ;

char  *getxtsj()
{ 
    time_t  tv ;
    struct  tm   *tmp ;
    static  char  buf[20] ;
    tv = time( 0 ) ;
    tmp = localtime(&tv) ;
    sprintf(buf,"%02d:%02d:%02d",tmp->tm_hour , tmp->tm_min,tmp->tm_sec);
    return   buf ;

}

int main(int argc, char **argv)
{
    int msqid ;
    struct msqid_ds info ;
    struct msgbuf buf ;
    struct msgbuf buf1 ;
    int flag ;
    int sendlength, recvlength ;
    int key ;
    key = ftok("msg.tmp", 0x01 ) ;

    if ( key < 0 ) {
        perror("ftok key error") ;
        return -1 ;
    }
    msqid = msgget( key, 0600|IPC_CREAT ) ;
    if ( msqid < 0 ) {
        perror("create message queue error") ;
        return -1 ;
    }

    buf.mtype = 1 ;
    buf.status = 9 ;
    strcpy(buf.time, getxtsj()) ;
    strcpy(buf.mtext, "happy new year!") ;
    sendlength = sizeof(struct msgbuf) - sizeof(long) ;
    flag = msgsnd( msqid, &buf, sendlength , 0 ) ;
    if ( flag < 0 ) {
        perror("send message error") ;
        return -1 ;
    }

    buf.mtype = 3 ;
    buf.status = 9 ;
    strcpy(buf.time, getxtsj()) ;
    strcpy(buf.mtext, "good bye!") ;
    sendlength = sizeof(struct msgbuf) - sizeof(long) ;
    flag = msgsnd( msqid, &buf, sendlength , 0 ) ;
    if ( flag < 0 ) {
        perror("send message error") ;
        return -1 ;
    }
    system("ipcs -q") ;
    return 0 ;
}

接收端

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

#define TEXT_SIZE  512

struct msgbuf
{
    long mtype ;
    int  status ;
    char time[20] ;
    char mtext[TEXT_SIZE] ;
}  ;

int main(int argc, char **argv)
{
    int msqid ;
    struct msqid_ds info ;
    struct msgbuf buf1 ;
    int flag ;
    int  recvlength ;
    int key ;
    int mtype ;
    key = ftok("msg.tmp", 0x01 ) ;
    if ( key < 0 ) {
        perror("ftok key error") ;
        return -1 ;
    }
    msqid = msgget( key, 0 ) ;
    if ( msqid < 0 ) {
        perror("get ipc_id error") ;
        return -1 ;
    }
    recvlength = sizeof(struct msgbuf) - sizeof(long) ;
    memset(&buf1, 0x00, sizeof(struct msgbuf)) ;
    mtype = 1 ;
    flag = msgrcv( msqid, &buf1, recvlength ,mtype,0 ) ;
    if ( flag < 0 ) {
        perror("recv message error\n") ;
        return -1 ;
    }
    printf("type=%d,time=%s, message=%s\n", buf1.mtype, buf1.time,  buf1.mtext) ;
    system("ipcs -q") ;
    return 0 ;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值