今天我们来说说进程间通信的另一种方法————消息队列。
1.什么是消息队列?
消息队列是操作系统提供的队列,他提供了一种从一个进程向另一个进程发送一个有类型数据块的方法来实现通信。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
2.进程间通信(IPC)必须要知道的数据结构?
1.IPC_Perm他是共有的数据结构,表明消息队列权限方面的信息。
2.msgid_ds 他是消息队列特有的数据结构。
结构如下:
一个消息队列结构
struct msqid_ds 中主要数据成员介绍如下:
struct msqid_ds
{
struct ipc_perm msg_perm;
struct msg *msg_first; /*消息队列头指针*/
struct msg *msg_last; /*消息队列尾指针*/
__kernel_time_t msg_stime; /*最后一次插入消息队列消息的时间*/
__kernel_time_t msg_rtime; /*最后一次接收消息即删除队列中一个消息的时间*/
__kernel_time_t msg_ctime;
struct wait_queue *wwait; /*发送消息等待进程队列*/
struct wait_queue *rwait;
unsigned short msg_cbytes;
unsigned short msg_qnum; /*消息队列中的消息个数*/
unsigned short msg_qbytes;
__kernel_ipc_pid_t msg_lspid; /*最后一次消息发送进程的pid*/
__kernel_ipc_pid_t msg_lrpid; /*最后一次消息发送进程的pid*/
};
那么具体我们应该怎么使用呢?
我们举个例子来说一下:
1. 创建消息队列
函数:int msgget(key_t key,int msgflg)
返回值:成功返回消息队列描述符,失败返回-1。
key_t key:一般用ftok获取一个key值。(key_t ftok(const char* pathname(路径名),int proj_id(项目编号))成功返回key值,失败返回-1。)
int msgflg:一般有两个参数,IPC_CREAT和IPC_EXCL。若只使用IPC_CREAT表示创建消息队列,若该消息队列已经存在则打开已存在的消息队列并返回,若不存在,则创建。IPC_EXCL不会被单独使用。若两者同时使用,则如果创建的消息队列存在,出错返回。不存在就创建。(所以 如果成功了 则创建的一定是一个全新的消息队列)
2. 销毁消息队列
函数:int msgctl(int msgid,int cmd,struct msqid_ds *buf)
返回值:成功0,失败-1
msgid:通过上一步的msgget已经获取到
cmd:有几个参数,如果是删除为IPC_RMID表示立即移除MsgQueue
buf:如何使用完全取决于第二个参数,若只是删除则可以被忽略。
3. 获取消息队列
只需要把获取消息队列改一下,创建结果的返回值就为消息队列的标识符。
4. 发送消息
函数:int msgsnd(int msgid,const void* msgp,size_t msgz,int msgflg)
返回值:成功0,失败-1;
msgid:就为msgid
msgp:为一个结构msgbuf结构。
struct msgbuf
{
long mtype; //消息类型
charmtext[n]; //消息内容
}
msgsz:消息的大小
msgflg:表示发送的方式,一般为0
5. 接收消息
函数:ssize_t msgrcv(intmsgid,void* msgp,size_t msgsz,long msgtype,int msgflg)
同发送消息参数一致
下来我们代码实现:
comm.h
#ifndef __COMM_H_
#define __COMM_H_
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/msg.h>
#include<string.h>
#define PATHNAME "." //pathname为当前路径
#define PROJ_ID 0x1234
#define SERVER_TYPE 1 //这里要定义两个type因为收的人要知道是谁发的,同理发的人也该给出谁要收
#define CLIENT_TYPE 2
#define SIZE 128
struct msgbuf{
long mtype;
char mtext[SIZE];
};
int CreatMsgQueue();
int GetMsgQueue();
int SendMsg(int msgid,long type,const char* _info);
int RecvMsg(int msgid,long type,char * out);
int DestroyMsgQueue(int msgid);
#endif
comm.c
#include"comm.h"
int CommMsgQueue(int flag) //公用的 因为创建和得到代码差不多
{
key_t _f = ftok(PATHNAME,PROJ_ID);
if(_f<0){
perror("ftok\n");
return -1;
}
int msg_id = msgget(_f,flag);
if(msg_id < 0){
perror("msg_id\n");
return -2;
}
return msg_id;
}
int CreateMsgQueue()
{
return CommMsgQueue(IPC_CREAT|IPC_EXCL|0666); //这时只需要调用接口即可,后面的0666为你想要创建消息队列文件的权限
}
int GetMsgQueue()
{
return CommMsgQueue(IPC_CREAT);
}
int SendMsg(int msgid,long type,const char* _info)
{
struct msgbuf msg;
msg.mtype = type;
strcpy(msg.mtext,_info);
if( msgsnd(msgid,&msg,sizeof(msg.mtext),0) < 0){
perror("msgsnd\n");
return -1;
}
return 0;
}
int RecvMsg(int msgid,long type,char * out)
{
struct msgbuf msg;
if(msgrcv(msgid,&msg,sizeof(msg.mtext),type,0) < 0){
perror("msgrcv\n");
return -1;
}
strcpy(out,msg.mtext);
return 0;
}
int DestroyMsgQueue(int msg_id)
{
if( msgctl(msg_id,IPC_RMID,NULL) < 0){
perror("magctl\n");
return -1;
}
return 0;
}
server.c
#include"comm.h"
int main()
{
int msgid = CreateMsgQueue();
printf("msg:%d\n",msgid);
char buf[SIZE];
while(1){ //先收后发
RecvMsg(msgid,CLIENT_TYPE,buf);
printf("client: %s\n",buf);
printf("Please Enter > ");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s>0){
buf[_s -1] = '\0';
SendMsg(msgid,SERVER_TYPE,buf);
}
}
DestroyMsgQueue(msgid);
return 0;
}
client.c
#include"comm.h"
int main()
{
int msgid = GetMsgQueue();
char buf[SIZE];
while(1){ //先发后收
printf("Please Enter > ");
fflush(stdout);
ssize_t _s = read(0,buf,sizeof(buf)-1);
if(_s>0){
buf[_s-1] = '\0';
SendMsg(msgid,CLIENT_TYPE,buf);
}
RecvMsg(msgid,SERVER_TYPE,buf);
printf("server:%s\n",buf);
}
return 0;
}
运行结果: