system-v :unix系统的第五个版本
system-v ipc features
- 独立于进程(不受进程存在与否的影响;为文件描述符相反,一个进程死亡,其中的文件描述符也会被销毁)
- 没有文件名,没有文件描述符(但有一套相类似的机制,如下)
- ipc对象具有 key 和 id
消息队列用法
- 定义一个唯一的key(ftok函数生成)
- 构造消息对象(msgget)
- 发送特定类型消息(msgsnd)
- 接收特定类型消息(msgrcv)
- 删除消息队列(msgctl)
ftok函数
功能:生成一个key / 键值
函数原型
key_t ftok(const char *path,int proj_id);
参数:(通过下面的路径和整数,可以生成唯一的key)
- path:一个合法路径
- proj_id:一个整数
返回值:
- 成功:合法键值
- 失败:-1
msgget函数
功能:创建一个消息队列 / 获取消息队列id(和open函数类似)
函数原型:
int msgget(key_t key,int msgflg);
参数:
-
key:消息队列的键值
-
msgflg(可用
位于
来选择多个选项):- IPC_CREAT:如果消息队列不存在则创建之,如果存在则打开返回;
- IPC_EXCL:单独使用IPC_EXCL是没有意义的;IPC_CREAT 和 IPC_EXCL 两个同时使用,如果消息队列不存在则创建之,如果存在则出错返回。
- mode:设置消息队列的访问权限,rwx,如
IPC_CREAT | 0666
返回值:
- 成功:返回创建的或者已经存在的消息队列的 id
- 失败:-1
msgsnd函数
功能:发送特定类型的消息到消息队列
函数原型:
int msgsnd(int msqid,
const void *msgp,
size_t msgsz,int msgflg);
参数:
- msqid:消息队列id
- msgp:指针,指向消息缓存区
struct msgbuf
{
long mtype;//消息标识,表示消息类型,接收消息时参考该类型进行接收
char mtext[1];//消息内容
}
-
msgsz:消息正文的字节数
-
msgflg:(表示发送行为)
- IPC_NOWAIT:非阻塞发送(就算消息队列满了或者不够放新的消息也会马上返回)
- 0:阻塞发送(消息队列满了或者放不下要发送的内容时阻塞)
返回值:
- 成功:0
- 失败:-1
msgrcv函数
功能:从消息队列读取(特定类型的)消息(一个进程调用msgsnd发送数据,另一个进程调用msgrcv接收消息)
函数原型:
ssize_t msgrcv(int msqid,void *msgp,
size_t msgsz,long msgtyp,int msgflg);
参数:
msqid
:消息队列id
msgp
:消息缓存区
msgsz
:消息正文的字节数
msgtyp
:要接受的消息的标识,由此判断消息的类型,与发送方结构体中的的 long type
成员对应。官方说明如下:
The argument msgtyp specifies the type of message requested as follows:
* If msgtyp is 0, then the first message in the queue is read.
* If msgtyp is greater than 0, then the first message in the queue of type msgtyp is read, unless MSG_EXCEPT was
specified in msgflg, in which case the first message in the queue of type not equal to msgtyp will be read.
* If msgtyp is less than 0, then the first message in the queue with the lowest type less than or equal to the abso-
lute value of msgtyp will be read.
msgflg:(设置读取模式)
- IPC_NOWAIT:非阻塞读取(就算读不到任何消息马上返回)
- 0:阻塞读取(当前消息队列为空时阻塞,知道有满足要求的消息发送至消息队列)
- MSG_NOERROR:截断消息(别的模式下,队列的数据长度短于要求,那么read会返回、报错,此模式不报错)
返回值:
- 成功:0
- 失败:-1
msgctl函数
功能:设置和获取 消息队列的相关属性
,一般用来删除一个消息队列
函数原型:
int msgctl(int msgqid,int cmd,
struct maqid_ds *buf);
参数:
-
msgqid:消息队列的id
-
cmd
- IPC_STAT:获取消息队列的属性信息,填充到buf里面
- IPC_SET:设置消息队列的属性,通过buf来保存、传递属性设置
- IPC_RMID:删除消息队列
-
buf:相关结构体缓冲区
返回值:
- 成功:0
- 失败:-1
测试程序
msg_send.c
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUFFER_SIZE 512
struct message
{
long msg_type; // 表示要发送的消息类型
char msg_text[BUFFER_SIZE];
};
int main()
{
int qid;
/* key_t 其实就是 int 类型 */
key_t key;
struct message msg;
//根据不同的路径和关键字产生对应的key
if((key = ftok("./tmp",11)) == -1)
{
printf("Fail ftok.\n");
exit(1);
}
//创建消息队列
if((qid = msgget(key,IPC_CREAT | 0666)) == -1) //或上读写属性
{
printf("Fail msgget.\n");
exit(1);
}
printf("Open queue %d.\n",qid);
while(1)
{
printf("Enter some message to the queue: ");
if((fgets(msg.msg_text,BUFFER_SIZE,stdin)) == NULL)
{
puts("No message.\n");
exit(1);
}
msg.msg_type = getpid();
//添加消息到消息队列
if((msgsnd(qid,&msg,strlen(msg.msg_text),0)) < 0) // 0表示阻塞发送
{
printf("Message posted.\n");
exit(1);
}
if(strncmp(msg.msg_text,"quit",4) == 0)
{
break;
}
}
exit(0);
}
msg_read.c
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUFFER_SIZE 512
struct message
{
// msgsnd 中,msg_type 必须大于0,并且与 msgrcv 倒数第二个参数相对应
long msg_type;
char msg_text[BUFFER_SIZE];
};
int main()
{
int qid;
key_t key;
struct message msg;
//根据不同的路径和关键字产生标准的key,两个参数要与发送方保持一致!!!
if((key = ftok("./tmp",11)) == -1)
{
printf("Fail ftok.\n");
exit(1);
}
//创建消息队列,和上面使用的是同一个消息队列
//所以两个参数保持一致
//若同key值的队列已经存在,那么就不会再创建一个消息队列了
if((qid = msgget(key,IPC_CREAT|0666)) == -1) // 或上读写权限
{
printf("Fail msgget.\n");
exit(1);
}
printf("Open queue %d.\n",qid);
do
{
//读取消息队列
memset(msg.msg_text,0,BUFFER_SIZE);
// 倒数第三个参数采用 strlen(msg.text) 为佳
if(msgrcv(qid,(void*)&msg,BUFFER_SIZE,0,0) < 0)
{
printf("Fail msgcrv.\n");
exit(1);
}
printf("The message from process %ld : %s",msg.msg_type,msg.msg_text);
}while(strncmp(msg.msg_text,"quit",4));
//从系统内核中移走消息队列
if((msgctl(qid,IPC_RMID,NULL)) < 0)
{
printf("Fail msgctl.\n");
exit(1);
}
exit(0);
}
补充
在消息队列中,例如函数msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg);这个函数调用的时候,msgsz最大只能为8192,也就是2的16次方。可以看出这里的msgsz大小限制在一个short型。超过这个大小就会出错——invalid argument。并不是其他人所说的只要msgsz是mtext的大小就不会出错,如果sizeof(mtype)+sizeof(mtext)<=8192时,msgsz为sizeof(mtype)+sizeof(mtext)大小也没有关系。
当然msgsz这个大小也不是不可以改变,如果要变,就去内核代码里面关于实现消息队列的程序中把这个限制改变一下就好。(我没试过,应该可以),一般是在内核源码中的ipc文件夹中会有mqueue.c这个c语言程序文件,里面会定义DFLT_MSGSIZEMAX为8192,这应该就是为什么msgsz最大为8192的原因,如果要改,可以改掉,然后重新编译内核。
可以设置比 8192 小的 buffer,如果接收程序未运行,那么 发送程序发出的数据会一直保存在buffer里面,新的会覆盖旧的。
此外,我们还需要学习两个重要的命令
前面我们说过,消息队列需要手动删除IPC资源
ipcs:显示IPC资源
ipcrm:手动删除IPC资源
- 同一个进程中,若一个 msgrcv 在阻塞等待消息的过程,进程执行了另一个msgsnd或者msgrcv,那么这个阻塞的 msgrcv 必然会执行失败(返回 -1)。
阻塞接收方式可以采用:
msgrcv(migqid, &msgbuf, sizeof(msgbuf.text), type, 0)
,最后一个参数 0 表示阻塞接收。
也可以是
while(msgrcv(migqid, &msgbuf, sizeof(msgbuf.text), type, IPC_NOWAIT) < 0);
shell下删除消息队列 ipcrm -q 消息队列id
或者 c中 msgctl(消息队列号,IPC_RMID, NULL)
,消息队列不再存在 (ipcs -q
),对应相应的接收发送函数返回错误