消息队列:将消息队列按队列的方式组织成的链表,每个消息都是其中的一个节点;
注意:消息队列的长度及每个消息的大小是有限制的
消息队列的操作函数如下:
msgget
int msgget(key_t key,int msgflg);
该函数作用是:创建一个消息队列或访问一个已经存在的消息队列;成功返回标示符,出错返回-1;
其第一个参数是一个整数IPC键,由ftok函数产生,或者直接是常值IPC_PRIVATE;
msgflg是IPC_CREAT, IPC_EXCL与读写权限或的结果
msgsnd
int msgsnd(int msqid, const void *msgptr, int msgsz,int msgflg);
该函数的作用是:在msgget打开消息队列后,往消息队列发送消息;成功返回0, 出错返回-1;
msqid是消息队列的标示符;
msgsz指的是要发送的消息的长度;
msgflg参数设置的是一些标志位;设置当消息队列满等情况出现时的处理方式,如果msgflg设置为IPC_NOWAIT,则不发送消息并且立即返回-1;否则发送进程挂起等待。
msgptr是一个指向要发送的消息的指针,并指向缓冲区的第一个子段应为长整形,指定消息类型,消息内容存放在该缓冲区的紧跟在消息类型子段的区域;
msgptr是结构体指针,该结构体如下:
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgrcv
int msgrcv(int msqid, void *msgptr, int msgsz, long msgtyp, int msgflg);
该函数作用是从某个消息队列中读出一个消息
参数msqid是消息队列标示符;
msgptr和msgsnd函数中的作用相同,指向消息的类型子段;
msgsz是指要读出的消息的数据部分的大小,是msgrcv能返回的最大数据量,该长度只包含数据部分的大小,不包含类型子段大小;
msgtyp是想要读出的消息的类型;msgtyp > 0 时,该函数返回的是消息队列中类型值为msgtyp的第一个消息;
msgtyp = 0 时,该函数返回的是消息队列中的第一个消息;
msgtyp < 0 时, 该函数返回的是消息队列中类型值小于或等于该类型值绝对值的消息类型中最小的第一个消息;msgflg设置当消息队列满等情况出现时的进程处理方式,若设置为IPC_NOWAIT时,表示不发送消息并立即返回-1,否则发送进程挂起等待
msgctl
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
消息队列的控制函数
msqid是消息队列的id;
cmd是采取的控制操作,有三个值,如下所示:
参数值 说明
IPC_SET 设置消息队列的属性,将buf指向的结构体中的数值设置为消息队列的相关性
IPC_STAT 获取消息队列的属性信息并保存到buf指向的结构体中
IPC_RMID 移除ID为msqid的消息队列
buf是一个结构体指针,该结构体如下:
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 (nonstandard) */
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) */
};
以下是一个用来测试消息队列的这几个函数的使用的简单的单服务器单客户的程序代码:
服务器端的代码:
#include "utility.h"
#define MSGSND 100
#define MSGRCV 200
int main(int ac, char *av[])
{
key_t msg_key = ftok(av[1], 0xFF); //得到一个IPC键值
if(msg_key == -1){
printf("msg ftok error.\n");
exit(1);
}
int msg_id = msgget(msg_key, IPC_CREAT|IPC_EXCL|0666); //创建或打开一个消息队列
if(msg_id == -1){
printf("msgget error.\n");
exit(1);
}
Msg msg;
while(1){
printf("Ser:>");
scanf("%s", msg.MsgText); //输入消息的内容
if(strncmp(msg.MsgText, "quit", 4) == 0){
msgctl(msg_id, IPC_RMID, NULL); //删除消息队列
break;
}
msg.MsgType = MSGSND;
msgsnd(msg_id, &msg, strlen(msg.MsgText) + 1, IPC_NOWAIT); //发送消息队列的内容
msgrcv(msg_id, &msg, 256, MSGRCV, 0); //接收消息
printf("cli:>%s\n", msg.MsgText); //输出消息的内容
}
return 0;
}
以下是客户端的代码:
#include "utility.h"
#define MSGSND 200
#define MSGRCV 100
int main(int ac, char *av[])
{
key_t msg_key = ftok(av[1], 0xFF); //得到消息队列的键值
if(msg_key == -1){
printf("ftok error.\n");
exit(1);
}
int msg_id = msgget(msg_key, 0); //打开一个消息队列
if(msg_id == -1){
printf("msgget error.\n");
exit(1);
}
Msg msg;
while(1){
msgrcv(msg_id, &msg, 256, MSGRCV, 0); //接收消息
printf("ser:>%s\n", msg.MsgText);
printf("cli:");
scanf("%s", msg.MsgText);
msg.MsgType = MSGSND;
if(strncmp(msg.MsgText, "quit", 4) == 0){
msgsnd(msg_id, &msg, strlen(msg.MsgText) + 1, IPC_NOWAIT);
break;
}
msgsnd(msg_id, &msg, strlen(msg.MsgText) + 1, IPC_NOWAIT); //发送消息
}
return 0;
}
以下是utility.h头文件的代码:
#pragma once
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
void *__pad;
};
msg.h头文件
#include "utility.h"
typedef struct Msg{
long MsgType;
char MsgText[256];
}Msg;
makefile文件:
all:ser cli
ser:ser.cpp
g++ ser.cpp -o ser
cli:cli.cpp
g++ cli.cpp -o cli
.PHONY:clean
clean:
rm ser cli
若要实现一个服务器对多个客户,并且仅使用一个消息队列,则可以将客户的ID作为消息的类型,不过这种仅使用一个IPC通道很容易造成死锁,解决方法是:客户可以填满消息队列,防止服务器发送应答,于是客户阻塞在msgsnd中,服务器也是如此;
检测这种死锁的一个方法是约定服务器对消息队列总是非阻塞写
实现一个服务器对多个客户并且每个客户一个消息队列:服务器队列有共用的键,但是客户以IPC_PRIVATE创建各自私有的队列;每个客户把各自的私有队列的标示符传递给服务器,服务器把自己的应答发送到由客户指出的队列中; 这种设计的潜在问题是某个客户中途死亡,这种情况下它的死有队列中可能永远残留消息(或者至少到内核自举或i某个用户显示的删除该队列为止)。