消息队列的概念
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。我们可以通过发送消息
来避免命名管道的同步和阻塞问题。
来避免命名管道的同步和阻塞问题。
消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先入先出。消息队列与命名管道有一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。
消息队列数据结构
内核为每个IPC对象维护一个数据结构,消息队列,共享内存和信号量都有这样一个共同的数据结构。可用cat /usr/include/linux/ipc.h查看:
struct ipc_perm {
key_t __key; /* Key supplied to xxxget(2) */
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;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
可以看到第一个条目就是IPC结构体,后面的都是消息队列所私有的成员。
可以看出消息队列是用链表实现的。
消息队列相关函数
1.创建新消息队列或取得已存在消息队列
原型:int msgget(key_t key, int msgflg);
参数:
- key:可以认为是一个端口号,也可以由函数ftok生成。
- msgflg:
- IPC_CREAT 如果IPC不存在,则创建一个IPC资源,否则打开操作。
- IPC_EXCL:只有在IPC不存在的时候,新的IPC才建立,否则就产生错误。
如果将IPC_CREAT和IPC_EXCL标志一起使用,msgget()将返回一个新建的IPC标识;如果该IPC资源已存在,返回-1。
IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。
IPC_EXEL标志本身并没有太大的意义,但是和IPC_CREAT标志一起使用可以用来保证所得的对象是新建的,而不是打开已有的对象。
函数ftok可以把一个已存在的路径名和一个整数标识转换成一个key_t值,称为IPC键:
key_t ftok(const char *pathname, int proj_id);
2.向队列读/写消息
原型:
从队列中取消息:
从队列中取消息:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
将数据放到消息队列中:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
- msqid:消息队列的标识码
- msgp:指向消息缓冲区的指针,此位置⽤用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下:
struct msgbuf {
long mtype; /* type of message */
char mtext[1]; /* message text */
};
- msgsz:消息的大小。
- msgtyp:从消息队列内读取的消息形态。如果值为零,则表示消息队列中的所有消息都会被读取。
- msgflg:用来指明核心程序在队列没有数据的情况下所应采取的行动。如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG。当msgflg为0时,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。
3.设置消息队列属性
原型:int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
参数:
- msgctl 系统调用对 msgqid 标识的消息队列执行 cmd 操作,系统定义了 3 种 cmd 操作: IPC_STAT , IPC_SET , IPC_RMID
- IPC_STAT : 该命令用来获取消息队列对应的 msqid_ds 数据结构,并将其保存到 buf 指定的地址空间。
- IPC_SET : 该命令用来设置消息队列的属性,要设置的属性存储在buf中。
- IPC_RMID : 从内核中删除 msqid 标识的消息队列。
编写一个消息队列,实现两个进程间的通信
comm.h:#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define SIZE 1024
struct msg_st
{
long int msg_type;
char text[SIZE];
}ser_data, cli_data;
#include "comm.h"
int main()
{
ser_data.msg_type = 0;
int msg_id = msgget((key_t)1234, IPC_CREAT|0664);
if(msg_id == -1)
{
perror("msgget\n");
exit(1);
}
while(1)
{
ssize_t ret = msgrcv(msg_id, (void*)&ser_data, SIZE, ser_data.msg_type, 0);
if(ret == -1)
{
perror("server msgrcv\n");
exit(2);
}
printf("client# :%s", ser_data.text);
printf("server# :");
memset(ser_data.text, 0, SIZE);
fgets(ser_data.text, sizeof(ser_data.text), stdin);
if(strncmp(ser_data.text, "end", 3) == 0)
{
printf("server end, break\n");
break;
}
ser_data.msg_type = 1;
int tmp = msgsnd(msg_id, (void*)&ser_data, SIZE, 0);
if(tmp == -1)
{
perror("server msgsnd\n");
exit(3);
}
}
int ret = msgctl(msg_id, IPC_RMID, 0);
if(ret == -1)
{
perror("msgctl\n");
exit(4);
}
return 0;
}
client.c:
#include "comm.h"
int main()
{
int msg_id = msgget((key_t)1234, IPC_CREAT|0664);
if(msg_id == -1)
{
perror("msgget\n");
exit(1);
}
while(1)
{
printf("client# :");
fgets(cli_data.text, sizeof(cli_data.text), stdin);
cli_data.msg_type = 1;
int ret = msgsnd(msg_id, (void*)&cli_data, SIZE, 0);
if(ret == -1)
{
perror("client msgsnd\n");
exit(2);
}
memset(cli_data.text, 0, SIZE);
cli_data.msg_type = 0;
ssize_t tmp = msgrcv(msg_id,(void*)&cli_data, SIZE, cli_data.msg_type, 0);
if(tmp == -1)
{
perror("client msgrcv\n");
exit(3);
}
printf("server# :%s", cli_data.text);
if(strncmp(cli_data.text, "end", 3) == 0)
{
printf("msg end, break\n");
break;
}
}
int ret = msgctl(msg_id, IPC_RMID, 0);
if(ret == -1)
{
perror("msgctl\n");
exit(4);
}
return 0;
}
在两个terminal上分别运行server和client,互相发送消息:
可以看出,两个进程间成功进行了通信!