Linux进程通信之System V消息队列
System V消息队列是Open Group定义的XSI,不属于POSIX标准。System V IPC的历史相对很早,在上个世70年代后期有贝尔实验室的分支机构开发,80年代加入System V的系统内核中,后来商用UNIX系统基本都加入了System V IPC的功能。
System V消息队列相对于POSIX消息队列的区别主要是:
- POSIX消息队列的读操作总是返回消息队列中优先级最高的最早消息,而对于System V消息队列可以返回任意指定优先级(通过消息类型)的消息。
- 当向一个空消息队列中写入一个消息时,POSIX消息队列允许产生一个信号或启动一个线程,System V消息队列不提供类似的机制。
系统内核都会为每一个System V消息队列维护一个信息结构,在Linux 2.6.18中的定义如下:
<bits/msq.h> struct msqid_ds
{
struct ipc_perm msg_perm; /*IPC对象的属性信息和访问权限 */
__time_t msg_stime; /* time of last msgsnd command */
__time_t msg_rtime; /* time of last msgrcv command */
__time_t msg_ctime; /* time of last change */
unsigned long int __msg_cbytes; /* 当前队列中消息的字节数 */
msgqnum_t msg_qnum; /* 当前队列中消息的个数 */
msglen_t msg_qbytes; /* 队列允许存放的最大字节数 */
__pid_t msg_lspid; /* pid of last msgsnd() */
__pid_t msg_lrpid; /* pid of last msgrcv() */ //下面是保留字段
#if __WORDSIZE == 32
unsigned long int __unused1;
unsigned long int __unused2;
unsigned long int __unused3;
#endif
unsigned long int __unused4;
unsigned long int __unused5;
};
消息队列的结构可能的设计如下:
1 System V消息队列的创建和打开
System V消息队列的创建和使用会使用下面的函数接口:
#include <sys/msg.h>
int msgget(key_t key, int oflg);
//成功返回非负消息队列描述符,失败返回-1
key:消息队列的键,用来创建一个消息队列。System IPC都有一个key,作为IPC的外部标识符,创建成功后返回的描述符作为IPC的内部标识符使用。key的主要目的就是使不同进程在同一IPC汇合。key具体说可以有三种方式生成:
- 不同的进程约定好的一个值;
- 通过相同的路径名和项目ID,调用ftok()函数,生成一个键;
- 还可以设置为IPC_PRIVATE,这样就会创建一个新的,唯一的IPC对象;然后将返回的描述符通过某种方式传递给其他进程;
oflg:指定创建或打开消息队列的标志和读写权限(ipc_perm中的mode成员)。我们知道System V IPC定义了自己的操作标志和权限设置标志,而且都是通过该参数传递,这和open函数存在差别,open函数第三个参数mode用于传递文件的权限标志。System V IPC的操作标志包含:IPC_CREAT,IPC_EXCL,权限设置标志如下图:
下面是创建消息队列的测试代码:
#include <iostream>
#include <cstring>
#include <errno.h> #include <unistd.h>
#include <fcntl.h>
#include <sys/msg.h> using namespace std; #define PATH_NAME "/tmp/anonymQueue" int main(int argc, char **argv)
{
key_t key;
int fd; if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
{
cout<<"open file "<<PATH_NAME<<"failed.";
cout<<strerror(errno)<<endl;
return -1;
}
close(fd); key = ftok(PATH_NAME, 0);
int msgID; if ((msgID = msgget(key, IPC_CREAT | 0666)) < 0)
{
cout<<"open message queue failed...";
cout<<strerror(errno)<<endl;
return -1;
} cout<<"key:0x"<<hex<<key<<endl;
cout<<"descriptor id:"<<dec<<msgID<<endl;
}
在Linux 2.6.18下运行结果:
key:0x8015
descriptor id:917511
实现System V IPC的任何系统都提供两个特殊的程序ipcs和ipcrm。ipcs输出IPC的各种信息,ipcrm则用于删除各种System V IPC。由于System V IPC不属于POSIX标准,所以这两个命令也未被标准化。下面是通过ipcs命令来查看刚刚创建的消息队列。
[root@idcserver program]# ipcs -q -i 917511
Message Queue msqid=917511
uid=0 gid=0 cuid=0 cgid=0 mode=0666
cbytes=0 qbytes=65536 qnum=0 lspid=0 lrpid=0
send_time=Not set
rcv_time=Not set
change_time=Wed Aug 7 16:39:46 2013
2 System V消息队列的使用
System V消息队列的写入消息使用下面的函数接口:
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//成功返回0,失败返回-1
msqid:消息队列的描述符;
msgp:指向存放消息的缓冲区,该缓冲区中包含消息类型和消息体两部分内容。该缓冲区的结构是由用户定义的,在<sys/msg.h>中有关于该缓冲区结构定义的参版考模:
struct msgbuf
{
long int mtype; /* type of received/sent message */
char mtext[1]; /* text of the message */
};
缓冲区的开头是一个long型的消息类型,该消息类型必须是一个非负数。紧跟在消息类型后面的是消息体部分(如果消息长度大于0),参考模版中定义的mtext只是说明消息体,该部分可以自定义长度。我们自己的应用都会定义特定的消息结构。
msgsz:缓冲区中消息体部分的长度;
msgflg:设置操作标志。可以为0,IPC_NOWAIT;用于在消息队列中没有可用的空间时,调用线程采用何种操作方式。
标志为IPC_NOWAIT,表示msgsnd操作以非阻塞的方式进行,在消息队列中没有可用的空间时,msgsnd操作会立刻返回。并指定EAGAIN错误;
标志为0,表示msgsnd操作以阻塞的方式进行,这种情况下在消息队列中没有可用的空间时调用线程会被阻塞,直到下面的情况发生:
- 等到有存放消息的空间;
- 消息队列从系统中删除,这种情况下回返回一个EIDRM错误;
- 调用线程被某个捕捉到的信号中断,这种情况下返回一个EINTR错误;
System V消息队列的读取消息使用下面的函数接口:
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//成功返回接收到的消息的消息体的字节数,失败返回-1
msqid:消息队列的描述符;
msgp:指向待存放消息的缓冲区,该缓冲区中将会存放接收到的消息的消息类型和消息体两部分内容。该缓冲区的结构是由用户定义的,和msgsnd相对应。
msgsz:缓冲区中能存放消息体部分的最大长度,这也是该函数能返回的最大数据量;该字段的大小应该是sizeof(msg buffer) - sizeof(long);
msgtyp:希望从消息队列中获取的消息类型。
- msgtyp为0,返回消息队列中的第一个消息;
- msgtyp > 0,返回该消息类型的第一个消息;
- msgtyp < 0,返回小于或等于msgtyp绝对值的消息中类型最小的第一个消息;
msgflg:设置操作标志。可以为0,IPC_NOWAIT,MSG_NOERROR;用于在消息队列中没有可用的指定消息时,调用线程采用何种操作方式。
标志为IPC_NOWAIT,表示msgrcv操作以非阻塞的方式进行,在消息队列中没有可用的指定消息时,msgrcv操作会立刻返回,并设定errno为ENOMSG。
标志为0,表示msgrcv操作是阻塞操作,直到下面的情况发生:
- 消息队列中有一个所请求的消息类型可以获取;
- 消息队列从系统中删除,这种情况下回返回一个EIDRM错误;
- 调用线程被某个捕捉到的信号中断,这种情况下返回一个EINTR错误;
标志为MSG_NOERROR,表示接收到的消息的消息体的长度大于msgsz长度时,msgrcv采取的操作。如果设置了该标志msgrcv在这种情况下回截断数据部分,而不返回错误,否则返回一个E2BIG错误。
下面是关于消息队列读写的测试代码:
#include <iostream>
#include <cstring>
#include <errno.h> #include <unistd.h>
#include <fcntl.h>
#include <sys/msg.h> using namespace std; #define PATH_NAME "/tmp/anonymQueue" key_t CreateKey(const char *pathName)
{
int fd; if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
{
cout<<"open file "<<PATH_NAME<<"failed.";
cout<<strerror(errno)<<endl;
return -1;
} close(fd); return ftok(PATH_NAME, 0);
} int main(int argc, char **argv)
{
key_t key;
key = CreateKey(PATH_NAME); int msgID;
if ((msgID = msgget(key, IPC_CREAT | 0666)) < 0)
{
cout<<"open message queue failed...";
cout<<strerror(errno)<<endl; return -1;
} if (fork() == 0)
{
char msg[] = "cute yuki...";
char *msgBuf = new char[sizeof(long) + sizeof(msg)];
long mtype = 1; memmove(msgBuf, &mtype, sizeof(mtype));
memmove(msgBuf + sizeof(mtype), msg, sizeof(msg)); for (int i = 1; i <= 5; ++i)
{
if (msgsnd(msgID, msgBuf, sizeof(msg), 0) < 0)
{
cout<<"send message "<<i<<"failed...";
cout<<strerror(errno)<<endl;
continue;
} cout<<"child: send message "<<i<<" success..."<<endl;
sleep(1);
} exit(0);
} char msgBuf[256];
long mtype;
int recvLen; for (int i = 1; i <= 5; ++i)
{
recvLen = msgrcv(msgID, msgBuf, 256 - sizeof(long), 0, 0);
if (recvLen < 0)
{
cout<<"receive message failed...";
cout<<strerror(errno)<<endl;
continue;
} memmove(&mtype, msgBuf, sizeof(long)); cout<<"parent receive a message:"<<endl;
cout<<"message type:"<<mtype<<endl;
cout<<"message body:"<<msgBuf + sizeof(long)<<endl;
}
return 0;
}
在Linux 2.6.18下的执行结果为:
child: send message 1 success...
parent receive a message:
message type:1
message body:cute yuki...
child: send message 2 success...
parent receive a message:
message type:1
message body:cute yuki...
child: send message 3 success...
parent receive a message:
message type:1
message body:cute yuki...
child: send message 4 success...
parent receive a message:
message type:1
message body:cute yuki...
child: send message 5 success...
parent receive a message:
message type:1
message body:cute yuki...
3 System V消息队列的控制操作
对System V消息队列的删除,属性的设置和获取等控制操作要使用下面的函数接口:
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//成功返回0,失败返回-1
msqid:消息队列的描述符;
cmd:控制操作的命令,SUS标准提供以下三个命令:
- IPC_RMID,删除一个消息队列。执行该命令系统会立刻把该消息队列从内核中删除,该消息队列中的所有消息将会被丢弃。这和已经讨论过的POSIX消息队列有很大差别,POSIX消息队列通过调用mq_unlink来从内核中删除一个消息队列,但消息队列的真正析构会在最后一个mq_close结束后发生。
- IPC_SET,根据buf的所指的值来设置消息队列msqid_ds结构中的msg_perm.uid,msg_perm.gid,msg_perm.mode,msg_qbytes四个成员。
- IPC_STAT,通过buf返回当前消息队列的msqid_ds结构。
- 在Linux下还有例如IPC_INFO,MSG_INFO等命令,具体可以参考Linux手册;
buf:指向msqid_ds结构的指针;
下面是测试代码:
#include <iostream>
#include <cstring>
#include <errno.h> #include <unistd.h>
#include <fcntl.h>
#include <sys/msg.h> using namespace std; #define PATH_NAME "/tmp/anonymQueue" key_t CreateKey(const char *pathName)
{
int fd;
if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
{
cout<<"open file "<<PATH_NAME<<"failed.";
cout<<strerror(errno)<<endl;
return -1;
} close(fd); return ftok(PATH_NAME, 0);
} int main(int argc, char **argv)
{
key_t key;
key = CreateKey(PATH_NAME); int msgID;
if ((msgID = msgget(key, IPC_CREAT | 0666)) < 0)
{
cout<<"open message queue failed...";
cout<<strerror(errno)<<endl;
return -1;
} msqid_ds msgInfo;
msgctl(msgID, IPC_STAT, &msgInfo); cout<<"msg_qbytes:"<<msgInfo.msg_qbytes<<endl;
cout<<"msg_qnum:"<<msgInfo.msg_qnum<<endl;
cout<<"msg_cbytes:"<<msgInfo.msg_cbytes<<endl;
return 0;
}
在Linux 2.6.18下的执行结果为:
msg_qbytes:65536
msg_qnum:2
msg_cbytes:26
关于消息队列中允许存放最大的字节数可以通过IPC_SET命令进行修改,该修改只能针对本消息队列生效。如下测试代码:
int main(int argc, char **argv)
{
key_t key;
key = CreateKey(PATH_NAME); int msgID;
if ((msgID = msgget(key, IPC_CREAT | 0666)) < 0)
{
cout<<"open message queue failed...";
cout<<strerror(errno)<<endl;
return -1;
} msqid_ds msgInfo;
msgctl(msgID, IPC_STAT, &msgInfo); cout<<"msg_qbytes:"<<msgInfo.msg_qbytes<<endl;
cout<<"msg_qnum:"<<msgInfo.msg_qnum<<endl;
cout<<"msg_cbytes:"<<msgInfo.msg_cbytes<<endl; msgInfo.msg_qbytes = 6553600;
if (msgctl(msgID, IPC_SET, &msgInfo) < 0)
{
cout<<"set message queue failed...";
cout<<strerror(errno)<<endl;
return -1;
} msgctl(msgID, IPC_STAT, &msgInfo);
cout<<"msg_qbytes:"<<msgInfo.msg_qbytes<<endl;
cout<<"msg_qnum:"<<msgInfo.msg_qnum<<endl;
cout<<"msg_cbytes:"<<msgInfo.msg_cbytes<<endl;
return 0;
}
在Linux 2.6.18下的执行结果为:
msg_qbytes:65536
msg_qnum:0
msg_cbytes:0
msg_qbytes:6553600
msg_qnum:0
msg_cbytes:0
4 System V消息队列的内核限制
对System V IPC,系统往往会存在一些限制,对于消息队列,在Linux2.6.18中,系统内核存在以下限制:
[root@idcserver program]# sysctl -a |grep msg
kernel.msgmnb = 65536 //一个消息队列上允许的最大字节数
kernel.msgmni = 16 //系统范围内允许存在的最大消息队列数
kernel.msgmax = 65536 //每个消息的最大字节数
对于System V消息队列一般内核还有一个限制:系统范围内的最大消息数,在Linux下这个限制由msgmnb*msgmni决定。
上面已经说过可以通过IPC_SET来设置使用中的消息队列的最大字节数。但是要在系统范围内对内核限制进行修改,在Linux下面可以通过修改/etc/sysctl.conf内核参数配置文件,然后配合sysctl命令来对内核参数进行设置。例如下面示例:
[root@idcserver program]#echo "kernel.msgmnb = 6553600" >>/etc/sysctl.conf
[root@idcserver program]#echo "kernel.msgmni = 100" >>/etc/sysctl.conf
[root@idcserver program]#echo "kernel.msgmax = 6553600" >>/etc/sysctl.conf
[root@idcserver program]#sysctl -p
[root@idcserver program]# sysctl -a |grep msg
kernel.msgmnb = 6553600
kernel.msgmni = 100
kernel.msgmax = 6553600
Aug 8, 2013 AM 08:54 @lab<T...T>
Linux进程通信之System V消息队列的更多相关文章
- Linux进程通信之System V共享内存
前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而Sys ...
- linux c编程:System V消息队列一
消息队列可以认为是一个消息链表,System V 消息队列使用消息队列标识符标识.具有足 够特权的任何进程都可以往一个队列放置一个消息,具有足够特权的任何进程都可以从一个给定队列读出一个消息.在某个进 ...
- linux网络编程之system v消息队列(二)
今天继续学习system v消息队列,主要是学习两个函数的使用,开始进入正题: 下面则开始用代码来使用一下该发送函数: 在运行之前,先查看一下1234消息队列是否已经创建: 用上次编写的查看消息队列状 ...
- linux网络编程之system v消息队列(一)
经过上次对于进程通讯的一些理论的认识之后,接下来会通过实验来进一步加深对进程通讯的认识,话不多说,进入正题: 其实还可以通过管道,但是,管道是基于字节流的,所以通常会将它称为流管道,数据与数据之间是没 ...
- Linux IPC实践(6) --System V消息队列(3)
消息队列综合案例 消息队列实现回射客户/服务器 server进程接收时, 指定msgtyp为0, 从队首不断接收消息 server进程发送时, 将mtype指定为接收到的client进程的pid ...
- Linux IPC实践(5) --System V消息队列(2)
消息发送/接收API msgsnd函数 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 参数 msgid: 由ms ...
- Linux IPC实践(4) --System V消息队列(1)
消息队列概述 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法(仅局限于本机); 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值. 消息队列也有管道一样的不足: ...
- 利用System V消息队列实现回射客户/服务器
一.介绍 在学习UNIX网络编程 卷1时,我们当时可以利用Socket套接字来实现回射客户/服务器程序,但是Socket编程是存在一些不足的,例如: 1. 服务器必须启动之时,客户端才能连上服务端,并 ...
- 进程间通信 System V 消息队列
1.msgget (key_t ket,int flag) ; //创建一个新的消息队列或者访问一个已存在的消息队列 2.msgsnd(int msid, const void *ptr ,size_ ...
随机推荐
- Golang几个常用记录日志对比
go语言有一个标准库,log,提供了最基本的日志功能,但是没有什么高级的功能,如果需要高级的特性,就需要使用第三方包,下面是一些候选的包: go_tmlog https://code.google.c ...
- 初识MFC,WinForm,WPF,Q't
MFC和QT是C++中常见的GUI框架,而WinForm和WPF是C#中常用的框架,不过我们一般很少叫WinForm框架,可能直接叫图形控件类库更多点.反正只是个称呼罢了,爱咋叫就咋叫.另外WinFo ...
- MyEclipse10导入工程jsp报错问题
好多时候,再用myecplise进行项目开发的时候,遇到导入工程的时候,工程内的jsp页面好多都报错.这是什么原因造成的呢? 我对于我遇到的问题及解决方法,跟大家分享一下. 我的Jsp页面报错的原 ...
- asp.net mvc 使用Ajax
使用asp.net mvc 调用Action方法很简单. 一.无参数方法. 1.首先,引入jquery-1.5.1.min.js 脚本,根据版本不同大家自行选择. <script src=&qu ...
- Java的平台无关性
转载自:http://www.cnblogs.com/Y/archive/2011/03/22/JavaVM_Learning_Chapter2_Platform_Independence.html ...
- HDU5873:Football Games
题目链接: Football Games 分析: 先将分数排序,然后 设当前队编号为p,设个指针为p+1,然后p>1,每次p-=2,指针右移一位p==1,指针指向的队-=1p==0,从指针开始到 ...
- Strider安装(Ubuntu)
安装: git clone https://github.com/Strider-CD/strider.git && cd strider nam install 然而还是出现一大波错 ...
- iOS tableview 优化总结
根据网络上的优化方法进行了总括.并未仔细进行语言组织.正在这些优化方法进行学习,见另一篇文章 提高app流畅度 1.cell子控件创建写在 initWithStyle:reuseIdentifier ...
- poj 1005 I Think I Need a Houseboat
#include <iostream> using namespace std; const double pi = 3.1415926535; int main() { ;; doubl ...
- JIT(动态编译)和AOT(静态编译)编译技术比较
Java 应用程序的性能经常成为开发社区中的讨论热点.因为该语言的设计初衷是使用解释的方式支持应用程序的可移植性目标,早期 Java 运行时所提供的性能级别远低于 C 和 C++ 之类的编译语言.尽管 ...