1.消息队列(链表实现的)概念
- 消息队列提供了一个从一个进程向另一个进程发送一块有效数据块的方法
- 每个数据块都被认为是有一个类型,接受者进程接受的数据块可以有不同的类型值
- 我们通过发送消息消息来避免命名管道的同步与阻塞问题。
- 消息队列是基于信息的,而管道四基于字节流的,并且消息队列不一定是先进先出的。
- 消息队列也有管道一样的不足,就是每个消息的最大长度是有上限(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。
2.IPC对象数据结构/usr/include/linux/ipc.h
-
内核为每个IPC对象维护一个数据结构
struct ipc_perm{
key_t _key;
uid_t uid;
gid_t cuid;
gid_t cgid;
unsigned short mode;
unsigned short _seq;
}
key(唯一性)内容是什么并不关心
mode是设置的权限
消息队列,共享内存与信息量都有这样一个结构。
3.消息队列结构/usr/include/linux/msg.h
struct msqid_ds
{
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 */
}
4.消息队列在内核中的表示
5.消息队列相关的函数
1).ftok:创造消息队列的key
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
2).msgget:用来创造和访问一个消息队列
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数:
key:某个消息队列的名字
msgflg:由9个权限标志组成,它们的用法和创建文件时使用的 mode(权限)模式编址是一样的
msgflg:O_CREAT与O_EXCL
1.如果同时被设置创建消息队列创建时已经有了则出错返回,如果成功创建可保证此消息队列是全新的。
2.单独O_CREAT没有创建则创建,创建了就打开(open获取)
O_CREAT与O_EXCL都是宏,源于不同的库,它们的比特位不同使用时用| 运算。
返回值:成功就返回一个非负整数,即该消息队列的标识码,失败返回-1.
3).msgctl:创建队列的控制函数
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
msqid:msget函数返回的消息队列标识码
cmd:是将要采取的动作(有三个可取值)
IPC_STAT:把msqid_ds结构中的数据设置为消息队列的当前关联值
IPC_SET:在进程有足够权限的前提下,把消息队列的当前值设置为msqid_ds数据结构中给出的值
IPC_RMID:删除消息队列
返回值:成功返回0,失败返回-1
4).msgsnd函数:把一条消息添加到消息队列
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t
msgsz, int msgflg);
参数:
msqid:由msgget函数返回的消息队列标识符
msgp:是一个指针,指针指向准备发送的消息
msqsz:是msqp指向的消息长度,这个长度不含保存消息类型的long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
msgflg=IPC_NOWAIT表示队满不等待,返回EAGAIN错误
返回值:成功返回0,失败返回-1。
消息结构在两方面受到制裁
- 首先,它必须小于系统规定的上限值
- 其次,它必须以一个long int长整数开始,接受者函数开始利用这个长整数消息的类型
消息结构的参考形式如下
struct msgbuf{
long mtype;
char mtext[1]
}
5).msgrcv函数:从一个消息队列接受信息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
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);
参数:
msgid:由msgget函数返回的消息队列标识符
msgp:是一个指针,指针指向准备接受的消息
msgsz:是msqp指向的消息长度,这个长度不含保存消息类型的那个long int 长整型
msgtype:它可以实现接受优先级的简单形式
msgflg:控制着队列中没有响相应类型的消息可供接受时将要发生的事
返回值:成功返回实际放到接受缓存区里面去的字符个数,成功返回-1
说明:
6.实例
1.创建文件:client是用户端,server是服务端
2.makefile文件内容
.PHONY:all
all:client server
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -rf client server
3.comm.h文件内容
#ifndef _COMM_H__
#define _COMM_H__
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#define PATHNAME "."
#define PROJ_ID 0x6366
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
int CreatMsgQueue();
int GetMsgQueue();
int DestoryMsgQueue(int msgid);
int SendMsg(int msgid, int type, char *msg);
int RecvMsg(int msgid, int type , char out[]);
//消息队列结构体
struct buf{
long mtype;
char mtext[1024];
};
#endif
4.comm.c 文件
#include"comm.h"
static int CommMsgQueue(int flags)
{
key_t _key = ftok(PATHNAME,PROJ_ID);
if(_key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(_key,flags);
if(msgid < 0)
{
perror("msgget");
}
return msgid;
}
int CreatMsgQueue()
{
return CommMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}
int GetMsgQueue()
{
return CommMsgQueue(IPC_CREAT);
}
int DestoryMsgQueue(int msgid)
{
if(msgctl(msgid,IPC_RMID,NULL)<0)
{
perror("msgctl");
return -1;
}
return 0;
}
int SendMsg(int msgid ,int type,char *msg)
{
struct buf message;
message.mtype = type;
strcpy(message.mtext,msg);
if(msgsnd(msgid,(void*)&message,sizeof(message.mtext),0)<0){
perror("msgsnd");
return -1;
}
return 0;
}
int RecvMsg(int msgid,int type,char out[])
{
struct buf message;
if(msgrcv(msgid,(void*)&message,sizeof(message.mtext),type,0)<0){
perror("msgrve");
return -1;
}
strcpy(out,message.mtext);
return 0;
}
5.server.c文件
#include"comm.h"
int main()
{
int msgid = CreatMsgQueue();
char buf[1028];
while(1){
buf[0]= 0;
RecvMsg(msgid,CLIENT_TYPE,buf);
printf("client# %s\n",buf);
printf("Please Enter# ");
fflush(stdout);
ssize_t s =read(0,buf,sizeof(buf));
if(s>0){
buf[s-1] = 0;
SendMsg(msgid,SERVER_TYPE,buf);
printf("send done,wait recv....\n");
}
}
DestoryMsgQueue(msgid);
return 0;
}
6.client.c文件
#include"comm.h"
int main()
{
int msgid = GetMsgQueue();
char buf[1028];
while(1){
buf[0] = 0;
printf("Please Enter# ");
fflush(stdout);
ssize_t s =read(0,buf,sizeof(buf));
if(s>0){
buf[s-1]= 0;
SendMsg(msgid,CLIENT_TYPE,buf);
printf("send done ,wait ....\n");
}
RecvMsg(msgid,SERVER_TYPE,buf);
printf("send# %s\n",buf);
}
return 0;
}
7.先打开server端,再次打开client端,
7.错误报告
我们发现执行一次后,第二层执行会失败,因为已经存在一个消息队列,我们使用ipcs -q
可以查看消息队列,使用ipcrm -q id
可以删除该消息队列,删除后便可再次成功执行。