消息队列
[1]消息队列是有类型的
与管道不同,消息队列中的数据是有[类型编号]的===>因此进程在通信的时,接收方可以接收指定[类型编号]的数据。
管道 | 消息队列 |
---|---|
流管道 | 有边界 |
流管道 | 可以后进/先出 |
[2]消息队列与命名管道的比较
- [相同点]:与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。而且它们对每个数据都有一个最大长度的限制。
- [不同点]:与命名管道相比,消息队列的优势在于
-1. 消息队列可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难
-2. 同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法
-3. 接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收
消息大小的3大限制
限制 | 查看命令 |
---|---|
最大消息长度限制 | cat /proc/sys/kernel/msgmax |
消息队列总的字节数 | cat /proc/sys/kernel/msgmnb |
消息条目数 | cat /proc/sys/kernel/msgmni |
注:Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。
消息队列API
int msgget
(key_t key, int msgflg);
功能:创建消息队列/打开已有的消息队列
参数
key:0x1110,IPC_PRIVATE(宏定义–0)
msgflg:权限标识(如,0666),可以 | 上IPC_CREAT / IPC_EXCL
返回值
成功:返回非0值,即msg标识号
失败:返回-1,errno被设置为合适的值
IPC_PRIVATE特殊情况
① 当key==IPC_PRIVATE时,msgget创建的msg的标识符key_t是0x00000000
② key_t最后是0,使用IPC_PRIVATE以后,IPC_CREAT|IPC_EXCL不会检查到已存在的消息队列,没有实质性的作用—>每次重新调用以后都会创建新的消息队列,msg_id都不一样—>意味着即使msg_id传送给其他进程,其他进程也不能用(可以通过fork–血缘关系使用)
③ IPC_PRIVATE消息队列,只能在自己家族中使用;不在没有血缘关系的进程间使用。
[1]msgget(0x1100,0666);
if msg存在,则打开
if msg不存在,则errno=ENOENT
[2]msgget(0x1100,0666|IPC_CREAT);
if msg存在,则打开
if msg不存在,则创建
[3]msgget(0x1100,0666|IPC_CREAT|IPC_EXCL);
if msg存在,则errno=EEXIST
if msg不存在,则创建
多次执行msgget创建IPC_PRIVATE消息队列的执行结果
--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0x00000000 98304 gjw 666 0 0
0x00000000 131073 gjw 666 0 0
0x00000000 163842 gjw 666 0 0
0x00000000 196611 gjw 666 0 0
执行结果分析:
1.[键]:全部是0x00000000
2.[msqid]:不一样
int msgctl
(int msqid, int cmd, struct msqid_ds *buf);
返回值:成功返回0,失败返回-1
参数
msqid: 由msgget函数返回的消息队列标识码
cmd:是将要采取的动作,(有三个可取值)
- IPC_STAT:获取消息队列的状态属性–msgctl(msg_id,IPC_STAT,&buf);
- IPC_SET:设置消息队列的状态属性–msgctl(msg_id,IPC_SET,&buf);
- IPC_RMID:删除消息队列–相当于执行命令:ipcrm msg <消息队列ID>
//获取,设置,删除
int main()
{
int msg_id;
int ret = 0;
msg_id = msgget(0x1236,0666);
if(-1 == msg_id)
{
perror("msgget");
return -1;
}
struct msqid_ds buf;
memset(&buf,0,sizeof(struct msqid_ds));
//[1] IPC_STAT [获取]消息队列的状态
ret = msgctl(msg_id,IPC_STAT,&buf);
if(-1 == ret)
{
perror("msgctl");
return -1;
}
printf("perm:%o\n",buf.msg_perm.mode);//权限
printf("bytes:%d\n",(int)buf.__msg_cbytes);//消息队列中的字节数
printf("number of msg:%d\n",(int)buf.msg_qnum);//消息条目数
printf("Input any key to delete the msg...\n");getchar();
//[2] IPC_SET [修改]消息队列
buf.msg_perm.mode = 0644;
ret = msgctl(msg_id,IPC_SET,&buf);
if(-1 == ret)
{
perror("msgctl");
return -1;
}
printf("Input any key to delete the msg...\n");getchar();
//[3] IPC_RMID从内核删除消息队列
ret = msgctl(msg_id,IPC_RMID,&buf);
if(-1 == ret)
{
perror("msgctl");
return -1;
}
printf("delete successfully\n");
return 0;
}
int msgsnd
(int msqid, const void *msgp, size_t msgsz, int msgflg);
返回值:成功返回0;失败返回-1
参数
msqid:消息队列标识码
msgp:指向准备发送的数据
msgsz:是msgp指向的消息长度(这个长度不含保存消息类型的那个long int长整型),仅仅是char mtext的字符串的长度。
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情(msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误)
ssize_t msgrcv
(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数
msgp:接收消息,存放的位置
msgsz:接收消息的长度
msgtyp:消息类型
msgflg
msgsnd、msgrcv使用的struct msgbuf
其中,消息结构体:struct msgbuf
,它用作msgrcv/msgsnd的第二个参数 。消息结构体一般都是自定义的:
1.消息结构体中的第一个属性必须是long mtype(用于标识消息类型)
2. 后面的内容可以自定义
struct msgbuf {
long mtype; //消息类型的标识 %ld
//........
}
如:
typedef struct mesgbuf {
long mtype; /* message type, must be > 0 */
int age;
char name[1024]; /* message data */
int score;
}msgbuf_t;
使用的示例代码
struct mesgbuf msg_buf;
msgsnd(msg_id,&msg_buf ,sizeof(msg_buf ),IPC_NOWAIT);
msgrcv(msg_id,&msg_buf ,sizeof(msg_buf ),type,IPC_NOWAIT);
printf("recv message: type=%ld, age=%d, score=%d, name=%s\n",
buf.mtype,buf.age,buf.score,buf.name);
C/S模型的多进程之间消息队列通信
程序设计思想:
[1]客户端发给服务器消息类型总是1;服务器接收的消息类型总是1
[2]服务器端回给客户端的type是对方进程号
[3]客户端只接收(消息类型=自己进程号)的消息
<服务器代码>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#define MSG_BUF 256
typedef struct mesgbuf
{
long mtype;
long pid; //message type
char mtext[MSG_BUF];
}msgbuf_t;
#define MSGTOSRV 1
int main()
{
int ret = 0;
int msg_id = 0;
msgbuf_t my_msg;
key_t key;
long type;
key = ftok("./",'a');
msg_id = msgget(key,IPC_CREAT|IPC_EXCL|0666);//创建
if(msg_id == -1)
{
if(errno == EEXIST)
{
//printf("EEXIST\n");
key = ftok("./",'a');
msg_id = msgget(key,IPC_CREAT|0666);
}
else
{
perror("msget");
exit(-1);
}
}
while(1)
{
memset(&my_msg,0,sizeof(my_msg));
//取消息的时候要指定type
ret = msgrcv(msg_id,&my_msg,sizeof(my_msg),MSGTOSRV,0);//接收( type=MSGTOSRV )消息
if(ret == -1)
{
perror("msgsnd");
exit(-1);
}
printf("recv message:type=%ld, cli_pid=%ld, recvmsg=%s\n",my_msg.mtype,my_msg.pid,my_msg.mtext);
//填充类型
my_msg.mtype = my_msg.pid;
//发消息之前要封装type
ret = msgsnd(msg_id,&my_msg,sizeof(my_msg),IPC_NOWAIT);//发送
if(ret == -1)
{
perror("msgsnd");
exit(-1);
}
printf("send message:type=%ld, cli_pid=%ld, sendmsg=%s\n",my_msg.mtype,my_msg.pid,my_msg.mtext);
}
return 0;
}
<客户端代码>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/types.h>
#include <sys/ipc.h>
#define MSG_BUF 256
typedef struct mesgbuf
{
long mtype;
long pid; //message type
char mtext[MSG_BUF];
}msgbuf_t;
#define MSGTOSRV 1
int main(int argc,char* argv[])
{
if(argc<2)
{
fprintf(stderr,"Usage:%s <input string> \n",argv[0]);
exit(EXIT_FAILURE);
}
int ret = 0;
int msg_id = 0;
msgbuf_t my_msg;
key_t key;
long type = getpid();
key = ftok("./",'a');
msg_id = msgget(key,IPC_CREAT|IPC_EXCL|0666);//创建
if(msg_id == -1)
{
if(errno == EEXIST)
{
printf("EEXIST\n");
key = ftok("./",'a');
msg_id = msgget(key,IPC_CREAT|0666);
}
else
{
perror("msget");
exit(-1);
}
}
memset(&my_msg,0,sizeof(my_msg));
//while(1)
{
my_msg.mtype=MSGTOSRV;
my_msg.pid=getpid();
strcpy(my_msg.mtext,argv[1]);
ret = msgsnd(msg_id,&my_msg,sizeof(my_msg),IPC_NOWAIT);//发送
if(ret == -1)
{
perror("msgsnd");
exit(-1);
}
printf("please touch any to recvmessage...\n");
getchar();
memset(&my_msg,0,sizeof(my_msg));
ret = msgrcv(msg_id,&my_msg,sizeof(my_msg),type,0);//接收/获取消息
if(ret == -1)
{
perror("msgsnd");
exit(-1);
}
printf("recv message:type=%ld, pid=%ld, recvmsg=%s\n",my_msg.mtype,getpid(),my_msg.mtext);
}
return 0;
}
程序隐藏的问题分析:
因为客户端和服务器既向消息队列中写数据,又从消息队列中读数据。说明操作该消息队列是无序的,无序的操作很容易让消息队列“空了”或“满了”。
举例:假设客户端和服务器都想向已经满的缓冲区中发数据:
1.客户端不能向消息队列中发数据
2.服务器不能向消息队列中发数据
===>产生死锁现象。
总结:产生死锁的根本原因是C/S都[双向]操控消息队列
解决方案:服务器端可以用[多进程]的方式实现消息队列[单一方向的回流]
(1)解决问题的核心:使C/S都按照一个方向去读,按照一个方向去写
,就可以避免这种情况
(2)设计思想
-1. 每次有新的客户端,都向同一个[总的]消息队列中发数据
-2. 服务器检查消息类型是1,再检查消息中的cli_pid,如果pid不存在,说明新的客户端来了,服务器就fork一个子进程,让子进程单独建立一个PRIVATE的消息队列。
[Q1] 该PRIVATE的消息队列干啥呢?[A1] 服务器的子进程发报文,客户端收报文。
[Q2] 客户端怎么收报文呢?[A2]客户端根据自己的pid收报文。
====>这样设计你会发现,整个消息队列将会变得十分"有序"