linux消息队列

来源:互联网 发布:linux 修改文件权限 编辑:程序博客网 时间:2024/06/05 06:34

经典进程间通信机制(IPC):管道、FIFO、消息队列、信号量以及共享储存。

这些机制允许在同一台计算机上运行的进程可以相互通信。但是当考察到不同计算机(通过网络相连)的进程相互通信时就必须借助网络通信机制(network IPC),在分布式计算环境中,为了集成分布式应用,开发者需要对异构网络环境下的分布式应用提供有效的通信手段。为了管理需要共享的信息,对应用提供公共的信息交换机制是重要的。

消息队列提供了一种在两个不相关的进程之间传递数据的简单高效的方法,其特点如下:
1)消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取。
2)消息队列允许一个或多个进程向它写入或者读取消息。
3)与无名管道、命名管道一样,从消息队列中读出消息,消息队列中对应的数据都会被删除。
4)每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的。
5)消息队列是消息的链表,存放在内存中,由内核维护。只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在于系统中。

1.ftok()

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

函数原型:
key_t ftok( const char * pathname , int proj_id );
参数:

pathname 就时你指定的文件名(该文件必须是存在而且可以访问的),id是子序号,虽 然为int,但是只有8个比特被使用(0-255)。
返回值: 成功时候返回key_t 类型的key值,失败返回-1

系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
函数原型:key_t ftok( const char * fname, int id ); (id>0)
fname就是你指定的文件名(已经存在的文件名),一般使用当前目录。

在一般的UNIX实现中,是将文件的索引节点号取出。(文件重建将会分配一个新的索引节点号)
ftok 返回值组成:hex(id)&0xff03 hex(节点号)&0xffff。
传入的id低8位+0x03+ 节点号的低16位。(test on redhat )
可通过 ls -l 查看文件节点值。

示例:

#include <stdio.h>  #include <unistd.h>  #include <stdlib.h>  #include <sys/types.h>  #include <sys/ipc.h>  #include <sys/msg.h>  #include <string.h>  int main(int argc, char *argv[])  {      key_t key;      int  msgqid;      key = ftok(".", 2012); // key 值      // 创建消息队列      msgqid = msgget(key, IPC_CREAT|0666);      return 0;  } 

这里写图片描述

2.msgget函数

头文件

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

函数原型: int msgget ( key_t key , int msgflg );
函数描述:建立消息队列
参数:
msgget()函数的第一个参数是消息队列对象的关键字(key),函数将它与已有的消息队
列对象的关键字进行比较来判断消息队列对象是否已经创建。而函数进行的具体操作是
由第二个参数,msgflg 控制的。它可以取下面的几个值:
IPC_CREAT :如果消息队列对象不存在,则创建之,否则则进行打开操作;
IPC_EXCL:和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建之,否则产生一个错误并返回。
返回值:
成功时返回队列ID,失败返回-1,错误原因存于error中
EEXIST (Queue exists, cannot create)
EIDRM (Queue is marked for deletion)
ENOENT (Queue does not exist)
ENOMEM (Not enough memory to create queue)
ENOSPC (Maximum queue limit exceeded)

3.msgsnd函数:

头文件

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

函数原型:int msgsnd ( int msgid , struct msgbuf*msgp , int msgsz, int msgflg );

函数描述:将消息送入消息队列

参数说明:
1)msgid
传给msgsnd()函数的第一个参数msqid 是消息队列对象的标识符(由msgget()函数得
到),第二个参数msgp 指向要发送的消息所在的内存,第三个参数msgsz 是要发送信息长度(字节数),可以用以下的公式计算:
msgsz = sizeof(struct mymsgbuf) - sizeof(long);
第四个参数是控制函数行为的标志,可以取以下的值:
0,忽略标志位;
IPC_NOWAIT,如果消息队列已满,消息将不被写入队列,控制权返回调用函数的线
程。如果不指定这个参数,线程将被阻塞直到消息被可以被写入。

2)msgbuf
对于消息队列的读写,都是以消息类型为准。消息类型相当于保险柜号码,A 往 1 号保险柜放东西,对方想取出 A 的东西必须也是从 1 号保险柜里取。同理,某一进程往消息队列添加 a 类型的消息,别的进程要想取出这进程添加的信息也必须取 a 类型的消息。
在学习消息队列读写操作前,我们先学习消息队列的消息格式:

smgbuf结构体定义如下:
struct smgbuf
{
long mtype; //消息的类型
char mtext [x] ; //消息正文,长度由msgsz决定
}

消息类型必须是长整型的,而且必须是结构体类型的第一个成员,类型下面是消息正文,正文可以有多个成员(正文成员可以是任意数据类型的)。至于这个结构体类型叫什么名字,里面成员叫什么名字,自行定义,没有明文规定。
3)msgsz:
消息正文的字节数。
4)msgflg:
函数的控制属性,其取值如下:

         0msgsnd() 调用阻塞直到条件满足为止。IPC_NOWAIT: 若消息没有立即发送则调用该函数的进程会立即返回。

返回: 0 on success
-1 on error: errno = EAGAIN (queue is full, and IPC_NOWAIT was asserted)
EACCES (permission denied, no write permission)
EFAULT (msgp address isn’t accessable – invalid)
EIDRM (The message queue has been removed)
EINTR (Received a signal while waiting to write)
EINVAL (Invalid message queue identifier, nonpositive
message type, or invalid message size)
ENOMEM (Not enough memory to copy message buffer)

4.msgrcv函数

头文件

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

函数定义:int msgrcv( int msgid , struct msgbuf* msgp , int msgsz , long msgtyp, int msgflg);

函数描述:从消息队列中读取消息

参数:
函数的前三个参数和msgsnd()函数中对应的参数的含义是相同的。

第四个参数mtype
指定了函数从队列中所取的消息的类型。函数将从队列中搜索类型与之匹配的消息并将
之返回。不过这里有一个例外。如果mtype 的值是零的话,函数将不做类型检查而自动返回队列中的最旧的消息。第五个参数依然是是控制函数行为的标志,取值可以是:
0,表示忽略;
IPC_NOWAIT,如果消息队列为空,则返回一个ENOMSG,并将控制权交回调用函数
的进程。如果不指定这个参数,那么进程将被阻塞直到函数可以从队列中得到符合条件
的消息为止。如果一个client 正在等待消息的时候队列被删除,EIDRM 就会被返回。如果 进程在阻塞等待过程中收到了系统的中断信号,EINTR 就会被返回。
MSG_NOERROR,如果函数取得的消息长度大于msgsz,将只返回msgsz 长度的信息,
剩下的部分被丢弃了。如果不指定这个参数,E2BIG 将被返回,而消息则留在队列中不 被取出。
当消息从队列内取出后,相应的消息就从队列中删除了。
msgbuf:结构体,定义如下:
struct msgbuf
{
long mtype ; //信息种类
char mtest[x];//信息内容 ,长度由msgsz指定
}

msgtyp: 信息类型。 取值如下:
msgtyp = 0 ,不分类型,直接返回消息队列中的第一项
msgtyp > 0 ,返回第一项 msgtyp与 msgbuf结构体中的mtype相同的信息
msgtyp <0 , 返回第一项 mtype小于等于msgtyp绝对值的信息

msgflg:取值如下:
IPC_NOWAIT ,不阻塞
IPC_NOERROR ,若信息长度超过参数msgsz,则截断信息而不报错。

返回值:
成功时返回所获取信息的长度,失败返回-1,错误信息存于error
Number of bytes copied into message buffer
-1 on error: errno = E2BIG (Message length is greater than
msgsz,no MSG_NOERROR)
EACCES (No read permission)
EFAULT (Address pointed to by msgp is invalid)
EIDRM (Queue was removed during retrieval)
EINTR (Interrupted by arriving signal)
EINVAL (msgqid invalid, or msgsz less than 0)
ENOMSG (IPC_NOWAIT asserted, and no message exists in the queue to satisfy the request)

5.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,却无足够的权限执行

对于消息队列的操作,我们可以类比为这么一个过程:假如 A 有个东西要给 B,因为某些原因 A 不能当面直接给 B,这时候他们需要借助第三方托管(如银行),A 找到某个具体地址的建设银行,然后把东西放到某个保险柜里(如 1 号保险柜),对于 B 而言,要想成功取出 A 的东西,必须保证去同一地址的同一间银行取东西,而且只有 1 号保险柜的东西才是 A 给自己的。

对于上面的例子,涉及到几个比较重要的信息:地址、银行、保险柜号码。
只有同一个地址才能保证是同一个银行,只有是同一个银行双方才能借助它来托管,只有同一个保险柜号码才能保证是对方托管给自己的东西。

而在消息队列操作中,键(key)值相当于地址,消息队列标示符相当于具体的某个银行,消息类型相当于保险柜号码。
同一个键(key)值可以保证是同一个消息队列,同一个消息队列标示符才能保证不同的进程可以相互通信,同一个消息类型才能保证某个进程取出是对方的信息。

示例:

//发送端程序#include <unistd.h>  #include <stdlib.h>  #include <stdio.h>  #include <string.h>  #include <sys/msg.h>  #include <errno.h>  #define MAX_TEXT 512   //定义text大小struct msg_st          //定义消息结构体{      long int msg_type;  // msgtyp > 0 ,返回第一项 msgtyp与 msgbuf结构体中的mtype相同的信息    char text[MAX_TEXT];  };  int main(){      int running = 1;      //运行标志位    struct msg_st data;  //msg_st结构体    char buffer[BUFSIZ];   //定义缓冲区,BUFSIZ为库中定义大小    int msgid = -1;       //消息队列对象的标识符    msgid = msgget((key_t)1234, 0666 | IPC_CREAT);  //创建消息队列关键字为1234,权限为666,如果消息队列对象不存在,则创建之,否则则进行打开操作;返回标识符msgid    if(msgid == -1)    //错误    {          fprintf(stderr, "msgget failed with error: %d\n", errno);          exit(EXIT_FAILURE);      }      //向消息队列中写消息,直到写入end      while(running)           //运行标志位是1时循环执行    {          printf("Enter some text: ");   //提示输入数据          fgets(buffer, BUFSIZ, stdin);  //stdin是标准输入,一般指键盘输入到缓冲区里的东西。        data.msg_type = 1;            strcpy(data.text, buffer);    //buffer里的数据复制到data        if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)   //向队列发送数据          {              fprintf(stderr, "msgsnd failed\n");    //错误            exit(EXIT_FAILURE);          }          //输入end结束输入          if(strncmp(buffer, "end", 3) == 0)              running = 0;          sleep(1);      }      exit(EXIT_SUCCESS);  }

//接收端程序#include <unistd.h>  #include <stdlib.h>  #include <stdio.h>  #include <string.h>  #include <errno.h>  #include <sys/msg.h>  struct msg_st{      long int msg_type;      char text[BUFSIZ];  };  int main()  {      int running = 1;      int msgid = -1;      struct msg_st data;      long int msgtype = 0; //注意1 msgtyp = 0 ,不分类型,直接返回消息队列中的第一项    //建立消息队列      msgid = msgget((key_t)1234, 0666 | IPC_CREAT);   //和发送程序相同。创建消息队列关键字为1234,权限为666,如果消息队列对象不存在,则创建之,否则则进行打开操作;返回标识符msgid。标识符都为1234    if(msgid == -1)      {          fprintf(stderr, "msgget failed with error: %d\n", errno);          exit(EXIT_FAILURE);      }      //从队列中获取消息,直到遇到end消息为止      while(running)      {          if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)  //        {              fprintf(stderr, "msgrcv failed with errno: %d\n", errno);              exit(EXIT_FAILURE);          }          printf("You wrote: %s\n",data.text);  //打印data.text        //遇到end结束          if(strncmp(data.text, "end", 3) == 0)              running = 0;      }      //删除消息队列      if(msgctl(msgid, IPC_RMID, 0) == -1)      {          fprintf(stderr, "msgctl(IPC_RMID) failed\n");          exit(EXIT_FAILURE);      }      exit(EXIT_SUCCESS);  } 
原创粉丝点击