前面我们所讲的 管道,不论是否匿名,都是半双工的,也就是说,一方只能读,一方只能写。
消息队列
消息队列,是消息的链表,存放在 内核中,一个消息队列由一个标识符(即 队列 ID)来表识。
特点
- 消息队列是面向记录的,其中的消息具有特定的格式及特定的优先级。
- 消息队列独立与发送与接收进程,进程终止时,消息队列及内容不会被删除。
- 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按照消息的类型读取。
所以:
a. B如何加消息?
b. A如何从队列拿消息?
消息队列常用 API
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
// 创建 或 打开一个消息队列,成功则返回消息队列 ID,失败则返回 -1
// key 消息队列的键值,函数将它与已有的消息队列对象的关键字进行比较来判断消息队列对象是否已经创建。
// 多个进程可以通过它来访问同一个消息队列,其中有一特殊值 IPC_PRIVATE 用于创建当前进程的私有消息队列。
// msgflg : 权限标志位。可以取下面的几个值:
// IPC_CREAT :如果消息队列对象不存在,则创建之,否则则进行打开操作; 使用时,要 IPC_CREAT|0777 来规定创建的消息队列的权限,0777(可读可写可执行)
// IPC_EXCL :和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建之,否则产生一个错误并返回 -1。
int msgsnd(int msqid,const void * msgp, size_t msgsz, int msgflg);
// 发送(添加)消息,成功返回 0,失败则返回 -1
// msqid 消息队列的 ID,
/* msgp: 指向消息结构的指针,该消息结构 msgbuf 通常如下:
struct msgbuf{
long mtype; // 消息类型,该结构必须从这个域开始
char mtext[1]; // 消息正文
} */
// msgsz : 消息正文的字节数(不包括消息类型指针变量)
/* msgflg : IPC_NOWAIT : 若此消息无法立即送达(如当前消息队列已满),函数立即返回
0 : msgsnd 调用发生 阻塞 直至发送成功。 */
int msgrcv(int msqid, void * msgp, size_t msgsz, long int msgtyp, int msgflg);
// 读取消息:成功返回消息数据的长度,失败返回 -1
// msqid : 消息队列的队列 ID
// msgp : 消息缓冲区,同 msgsnd() 函数的 msgp
// msgsz : 消息正文的字节数
/* msgtyp : = 0 : 接收消息队列中 第一个消息
> 0 : 接收消息队列中第一个类型为 msgtyp 的消息
< 0 : 接收消息队列中第一个类型值 不小于 msgtyp 绝对值且类型值最小的消息。 */
/* msgflg : MSG_NOERROR : 若返回的消息比 msgsz 字节多,则消息会截短到 msgsz 字节,且不通知消息发送进程
IPC_NOWAIT : 若在消息队列中,并没有相应的消息可以接收,则函数立即返回。
0 : msgsnd() 调用阻塞直到接收到一条相应类型的消息为止。 */
int msgctl(int msgqid, int cmd, struct msgid_ds * buf);
// 控制消息队列,成功返回 0, 失败返回 -1
// msqid : 消息队列 ID
/* cmd 命令参数 : IPC_STAT : 读取消息队列的数据结构 msqid_ds, 并将其储存在 buf 所指定的地址中
IPC_SET : 设置消息队列是数据结构 msqid_ds 中的 ipc_perm 域(IPC操作权限描述结构)值,这个值取自参数 buf
IPC_RMID : 从系统内删除消息队列的 msqid_ds 结构类型变量 */
ftok
系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该 id 值通过ftok函数得到。
函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。
ftok函数原型及说明如下:
头文件
#include <sys/types.h>
#include <sys/ipc.h>
函数说明
把从pathname导出的信息与id的低序8位组合成一个整数IPC键
函数原型
key_t ftok(const char *pathname, int id);
传入值
pathname :指定的路径名,此文件必须存在且可存取,一般使用当前目录
proj_id : 计划代号(project ID)
函数返回值
成功 : 返回key_t值(即IPC 键值)
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
每个文件都有其索引节点,命令 ls -i 可以查看当前目录下 文件的索引节点,
ls -a 可以看到 当前目录 . 上一级目录 .. (最后有演示)
出错 : -1,错误原因存于error中
附加说明
key_t一般为32位的int型的重定义
id是子序号。虽然是int类型,但是只使用8bits(1-255)。
eg :
key_t key;
key = ftok(".", 1); 也可写为: key = ftok(".",'z'); . 就是将fname设为当前目录,
————————————————————————————————
如何移除消息队列呢?我们使用 msgctl
msgctl
作用 : msgctl系统调用对 msqid 标识的消息队列执行cmd操作列。 (msgid 即:消息队列的 ID )
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函数声明:
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
函数参数:
msgqid : 消息队列的 id
cmd —— 使用命令 :
IPC_STAT 读取消息队列的数据结构msqid_ds,并将其存储在buf指定的地址中。
IPC_SET 设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
IPC_RMID 从系统内核中移走消息队列。
// 关于 msgqid_ds : 对于每一个队列都有一个msqid_ds来描述队列当前的状态。是一个结构体,具体定义在链接中详细讲有
// 我们这里仅讨论移除 消息队列的情况,所以我们这样做:
msgctl(msgqid,IPC_RMID,NULL);
返回值:
如果成功 : 0
如果失败 : -1
errno = EACCES (没有读的权限同时cmd 是IPC_STAT )
EFAULT (buf 指向的地址无效)
EIDRM (在读取中队列被删除)
EINVAL (msgqid无效, 或者msgsz 小于0 )
EPERM (IPC_SET或者IPC_RMID 命令被使用,但调用程序没有写的权限)
上代码实验:
./one
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf {
long mtype;
char mtext[128];
};
int main(){
struct msgbuf readbuf;
key_t key = ftok(".",1);
printf("key = %d\n",key);
int msgqid = msgget(key,IPC_CREAT|0777);
if(msgqid == -1 ){
printf("get que failuer \n");
}
msgrcv(msgqid,&readbuf,sizeof(readbuf.mtext),8,0);
printf("read from que : %s \n",readbuf.mtext);
struct msgbuf sendbuf = {9,"已经接收到消息,谢谢诶你"};
msgsnd(msgqid,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgqid,IPC_RMID,NULL);
return 0;
}
./secend
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
struct msgbuf {
long mtype;
char mtext[128];
};
int main(){
struct msgbuf sendbuf = {8,"this is massage from que's secend"};
struct msgbuf readbuf; // = (struct msgbuf *)malloc(sizeof(struct msgbuf));
key_t key = ftok(".",1);
printf("key = %d\n",key);
int msgqid = msgget(key,IPC_CREAT|0777);
if(msgqid == -1){
printf("get que failer \n");
}
int send_n = msgsnd(msgqid,&sendbuf,strlen(sendbuf.mtext),0);
if(send_n == -1) {
printf("send is failer \n");
}
else { printf("send is over \n");
}
msgrcv(msgqid,&readbuf,sizeof(readbuf.mtext),9,0);
printf("secend que : %s\n",readbuf.mtext);
msgctl(msgqid,IPC_RMID,NULL);
return 0;
}
运行结果:
以下为错误的代码,哥哥们俺找了一个小时,脑子不清醒了,哥哥们看出来哪里错了,跟弟弟说说,弟弟将感激不尽
./send 的进程
#include <stdio.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <string.h>
// int msgget(key_t key, int msgflg);
//
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
// struct msgbuf {
// long mtype; /* message type, must be > 0 */
// char mtext[1]; /* message data */
// };
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main(){
struct msgbuf sendbuf = {888,"this is massage from que"};
key_t key = ftok(".",'z');
printf("key = %d\n",key);
int msgID = msgget(key,IPC_CREAT|0777);
// int msgID = msgget(0x1234,IPC_CREAT|0777); 这样也可以,是直接使用了 linux 中,名为 0x1234 的一个消息队列
if(msgID == -1){
printf("get que failer\n");
}
msgsnd(msgID,&sendbuf,strlen(sendbuf.mtext),0);
msgctl(msgID,IPC_RMID,NULL);
return 0;
}
// 代码未表清晰,基本都没有利用返回值来验证 是否正确。
./read 的进程
手贱,误删了 草
#include <stdio.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main(){
struct msgbuf readbuf;
key_t key = ftok(".",'z');
printf("key = %d\n",key);
int msgID = msgget(msgID,IPC_CREAT|0777); //int msgID = msgget(0x1234,IPC_CREAT|0777);
if(msgID == -1){
printf("get que failer\n");
}
msgrcv(msgID,&readbuf,sizeof(readbuf.mtext),888,0);
printf("read from que %s\n",readbuf.mtext);
msgctl(msgID,IPC_RMID,NULL);
return 0;
}
运行结果:
双方的通信:
实现了一次 两个进程之间的相互通信,分别发送,读取消息。