消息队列:提供了一种从一个进程向另外一个进程发送数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立的接收含有不同的类型的数据结构,我们可以通过发送信息来避免命名管道的同步和阻塞问题,但是消息队列和命名管道一样,每个数据块都有一个最大的长度限制。
消息队列与管道的区别:
- 消息队列基于消息,管道基于字节流
- 消息队列的读取不一定是先入先出的
- 消息队列的生命周期是伴随内核,如果你没有删除他,那么关机前他一直存在
一条消息的最大值:msgmax
一条消息队列的总消息大小:msgmnb
系统能创建多少个消息队列:msgni
内核为每个IPC对象都维护一个数据结构
struct ipc_perm{//消息队列,共享内存,信号量共有的
key_t __key; //唯一标识符
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
struct msqid_ds {//私有
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in queue (non-standard) */
msgqnum_t msg_qnum; /* Current number of messages in queue */
msglen_t msg_qbytes; /* Maximum number of bytes allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
消息队列基本命令
查看消息队列:ipcs -q
我们可以看到这里有一条消息队列
删除消息队列:ipcrm -q +key
当我们执行删除消息队列指令之后,再次查看消息队列,刚才看到的那一条消息队列已经没有了。
基本函数
创建消息队列/打开消息队列
//创建消息队列或打开消息队列
int msgget(key_t key//相当于文件名
int msgflg)
//消息队列的权限 创建的话写IPC_CREAT,权限|后面
//即IPC_CREAT|0644
//若打开消息队列写曾经创建时候的权限或者0(内核自动查找)
返回值:id//相当于文件描述符子进程也可以用
参数:
key:
- 1234写死
- IPC_PRIVATE
- int ftok(char *path,int pro_id) //path是路径,必须存在,id的低八位必须非0。
msgflg
1.消息队列的权限,创建的话写IPC_CREAT|权限,如IPC_CREAT|0644- 若打开消息队列写曾经创建时候的权限或者0(内核自动查找)
返回值:id
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
int main()
{
int id=msgget(ftok(".",'a'),IPC_CREAT|0644);//. 路径绝对存在
if(id==-1)
perror("magget"),exit(1);
printf("msgget ok\n");
}
//我们在这里第一个参数用的是ftok函数
我们执行该程序,在使用查看消息的指令,可以看到我们创建了一个消息队列。
向消息队列中发送数据
int msgsnd(int msqid, //打开的消息队列
const void *msgp, //指向准备发送的消息地址
size_t msgsz,//消息大小,不包含channel字段
int msgflg);//一般填0,IPC_NOWAIT,如果消息队列满
//立马返回,error=EAGAIN
msgflg:用来致命核心程序在队列没有数据的情况的下所采取的行动.如果msgflg和常数IPC_NOWAIT合用,在msgsnd()执行时若是消息队列已满,则
msgsnd()不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码ENOMSG。当msgflg为0时,
msgrev()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理方式
msgp:指向消息缓冲区的指针,此地址用来暂时存储发送接收消息,是一个用户可定义的通用结构,结构如下
//结构体自己定义
struct msgbuf {
long mtype; //消息类型即通道号必须>0
char mtext[1]; //消息的数据占多大都没问题但是不能超过系统上限
};
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
struct msgbuf
{
long channel;
char text[1024];
};
int main(int argc,char *argv[])
{
struct msgbuf mb;
if(argc!=3)
{
printf("usage:%s channel msg\n",argv[0]);
exit(0);
}
int id=msgget(ftok(".",'a'),0);
if(id==-1)
perror("msgget"),exit(1);
mb.channel = atol(argv[1]);//转换为整数
strcpy(mb.text,argv[2]);
if(msgsnd(id,&mb,strlen(mb.text),0)==-1)
perror("msgsnd"),exit(0);
}
运行结果+分析:
从消息队列中读取数据
//从消息队列中读取数据
ssize_t msgrcv(int msqid,//打开的消息队列
void *msgp,
size_t msgsz,//放消息的大小,不包含类型大小,是真正放数据的大小
long msgtyp,
//>0:从指定的通道收取数据
//=0:返回消息队列中的第一条
//<0:读取<=|type|,并且读最小的类型
int msgflg);//一般写0,IPC_NOWAIT如果消息队列为空,不等待,立马返回
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
struct msgbuf
{
long channel;
char text[1024];
};
int main(int argc,char *argv[])
{
struct msgbuf mb={};//清空
long type;
if(argc!=2)
{
printf("usage:%s channel\n",argv[0]);
exit(0);
}
int id=msgget(ftok(".",'a'),0);
if(id==-1)
perror("msgget"),exit(1);
type=atol(argv[1]);
if(msgrcv(id,&mb,1024,type,0)==-1)
perror("msgrcv"),exit(1);
printf("%s\n",mb.text);
}
运行需要打开两个终端,
一个终端下,向消息队列中发送数据
另外一个终端收取消息
收取消息的终端如果运行:./rcv 0:那么就按顺序读取
收取消息的终端如果运行:./rcv + 负数:读取小于等于他的绝对值且最小的消息
删除消息队列,设置消息队列属性
int msgctl(int msqid//创建的消息队列的ID
,int cmd,//IPC_RMID(删除的话写它)
struct msqid_ds *buf);//删除的话不用,所以填0
参数:msgctl系统调用对msgqid标识的消息队列执行cmd操作系统定义了3种cmd操作:IPC_STAT,IPC_SET,IPC_RMID
IPC_STAT:该命令用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间.
IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中.
IPC_RMID:从内核中删除msqid标识的消息队列.
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
int main()
{
int id=msgget(ftok(".",'a'),0);//获取消息队列
if(id==-1)
perror("msgget"),exit(1);
msgctl(id,IPC_RMID,0);
}
运行结果:
我们可以看到,先查看消息队列 ,我们可以看到有一条消息队列,我们执行该程序之后,再次查看消息队列,该消息队列已经删除了
我们通常用该函数用来删除消息队列
双向通信——客户端/服务器端
我们可以使用消息队列来实现双向通信(客户端服务器端发送接收消息):
以下是代码实现:
//客户端
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
#include<string.h>
struct mymsgbuf
{
long type;
char buf[1024];
};
int main()
{
int id= msgget(1234,0);
if(id==-1)
perror("msgget"),exit(1);
struct mymsgbuf mb,rcvbuf;//定义mb缓存
int pid =getpid();
while(1)
{
memset(&mb,0x00,sizeof(mb));//清空缓存
*(int*)mb.buf=pid;
mb.type=1;
//read from keyboard
fgets(mb.buf+sizeof(int),sizeof(mb.buf)-sizeof(int),stdin);//从键盘读取一行
//send to servrer
msgsnd(id,&mb,strlen(mb.buf+sizeof(int))+sizeof(int),0);
//get msg from server
memset(&rcvbuf,0x00,sizeof(rcvbuf));
msgrcv(id,&rcvbuf,1024,pid,0);//从服务器中接收数据
printf("rcv=%s\n",rcvbuf.buf+sizeof(int));//显示屏幕
}
}
//服务器端
//在那个终端发送消息就在哪个终端接收消息
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<unistd.h>
#include<string.h>
struct mymsgbuf
{
long type;
char buf[1024];
};
int main(void)
{
int id = msgget(1234, IPC_CREAT|0644);
if(id == -1) perror("msgget"),exit(1);
struct mymsgbuf mb;//缓存区
while(1)
{
memset(&mb, 0x00, sizeof(mb));
msgrcv(id,&mb,1024,1,0);
mb.type = *(int*)mb.buf;
printf("%s\n",mb.buf+sizeof(int));
msgsnd(id, &mb,strlen(mb.buf+sizeof(int))+sizeof(int),0);
}
}
下面是代码的运行(我们要打开两个终端哦!):
客户端:我们在客户端发送消息
服务器端:在服务器端显示消息
消息 队列特点:
1.消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
2.同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
3.接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
4.可以实现双向通信。