进程通信之消息队列
1.消息队列基本概念
消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。与管道不同的是消息队列存放在内核中,只有内核重启或者显式地删除一个消息队列时,该消息队列才会被真正删除。
2.消息队列的创建与读写
(1)创建消息队列
消息队列随内核存在而存在,每个消息队列在系统范围内对应唯一的键值。要获得一个消息队列的描述符,只需要提供该消息队列的键值即可,该键值通常由函数ftok返回。
#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
ftok函数根据pathname和proj_id返回生成唯一的键值。该函数执行成功会返回一个键值,失败返回-1.
例程
#include<sys/types.h>/*ftok.c*/
#include<sys/ipc.h>
#include<stdlib.h>
#include<stdio.h>
int main()
{
	int i;
	for(i=1; i<=5; ++i)
	{
		printf("key[%d] = %lu \n", i, ftok(".", i));
	}
	return 0;
}
运行一下

注:参数pathname在系统中一定要存在且进程有访问权限,参数proj_id的取值范围为1~255.
ftok() 返回键值可提供给函数msgget,msgget() 根据这个键值创建一个新的消息队列或者访问一个已存在的消息队列。masgget定义在头文件sys/msg.h中。
#include<sys/msg.h>
int msgget(key_t key, int msgflg);//调用成功返回消息队列的描述符,失败返回-1
msgget的参数key即为ftok的返回值。msgflg是一个标志参数,以下为其可能的取值
·IPC_CREAT: 如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,则返回消息队列的描述符。
·IPC_EXCL:和IPC_CREATE一起使用,如果对应键值已经存在,则出错,返回-1.
注:IPC_EXCL单独使用无任何意义。
(2)写消息队列
创建消息队列以后就可以对其进行读写了。函数msgsnd用于向消息队列发送(写)数据。
#include<sys/msg.h> int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int magflg);
//成功返回0,失败返回-1
//msqid: 函数向msgid标识的消息队列发送一个消息
//msgp:指向发送的消息
//magflg:操作标志位。可以设置为0或者IPC_NOWAIT。如果magflg为0,则当消息队列已满时,
//msgsnd将会被阻塞,直到消息可以写入消息队列;如果magflg为IPC_NOWAIT,当消息队列已
//经满了的时候,msgsnd函数将不等待立即返回。
msgsnd函数常见的错误码有: EAGAIN,说明消息队列已满;EIDRN,说明消息队列已经被删除;EACCESS,说明无权访问消息队列。
例程
#include<stdio.h>/*sendmsg.c*/
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<error.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define BUF_SIZE	256
#define PROJ_ID		32
#define PATH_NAME	"."
int main()
{
	struct mymsgbuf
	{
		long msgtype;
		char ctrlstring[BUF_SIZE];
	}msgbuffer;
	int 	qid, msglen;
	key_t	msgkey;
	errno = 0;
	if((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)//获得键值
	{
		perror("ftok error!\n");
		exit(1);
	}
	if((qid = msgget(msgkey, IPC_CREAT|0666)) == -1)//创建消息队列
	{
		perror("msgget error!\n");
		exit(1);
	}
	msgbuffer.msgtype = 3;
	strcpy(msgbuffer.ctrlstring, "Hello, message queue!");
	msglen = sizeof(struct mymsgbuf) - 4;
	if(msgsnd(qid, &msgbuffer, msglen, 0) == -1)//写消息队列
	{
		printf("msgsnd error, error number is %d, error reason is %s .\n", errno, strerror(errno));
	}
	return 0;
}
运行一下后使用ipcs命令即可查看

因为运行了两次,所以message那块数量为2。
(3)读出消息队列
消息队列中有消息后,其他进程就可以愉快的读消息了。读消息的系统调用为msgrcv()
#include<sys/msg.h> int msgrcv(int msgid, struct msgbuf *msgp, size_t msgsz, long int msgtyp, int msgflg); /*函数调用成功返回读取消息的字节数,否则返回-1 msgid:消息队列中的描述符 msgp:读取的消息存储到msgp指向的消息结构中 msgsz:消息缓冲区大小 msgflg:操作标志位。msgflg可以为IPC_NOWAIT, IPC_EXCEPT, IPC_NOERROR三个常量。
IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时错误码为ENOMSG;
IPC_EXCEPT 与msgtype配合使用,返回队列中第一个类型不为msgtyp的消息;
IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将被丢弃。 */
msgrcv函数常见的错误有E2BIG,表示消息的长度大于msgsz;EIDRM,表示消息队列已被删除;EINVAL,说明msqid无效或者msgsz小于0。
先用刚刚的sendmsg程序给我们的消息队列添加几条消息

例程
#include<stdio.h>/*rcvmsg.c*/
#include<stdlib.h>
#include<errno.h>
#include<error.h>
#include<string.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define BUF_SIZE	256
#define PROJ_ID		32
#define PATH_NAME	"."
int main()
{
	struct mymsgbuf
	{
		long msgtype;
		char ctrlstring[BUF_SIZE];
	}mymsgbuffer;
	memset(mymsgbuffer.ctrlstring, '\0', BUF_SIZE);
	printf("sizeof(long) =  %u\n", sizeof(long));
	int 	qid, msglen;
	key_t	msgkey;
	errno = 0;
	if((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)
	{
		perror("ftok error!\n");
		exit(1);
	}
	if((qid = msgget(msgkey, IPC_CREAT|0666)) == -1)
	{
		perror("msgget error!\n");
		exit(1);
	}
	msglen = sizeof(struct mymsgbuf) - 4;
	if(msgrcv(qid, &mymsgbuffer, msglen, 3, 0) == -1)//运行程序以后有一个未知的越界错误,但是并不是数组造成的,很疑惑,也并没有打印出错误
	{
		printf("msgrcv error, error number %d, error reason is %s\n", errno, strerror(errno));
		exit(1);
	}
	printf("Get message is  %s \n", mymsgbuffer.ctrlstring);
	return 0;
}
运行一下

取出后消息队列相应减少(我取了两次)

3.获取和设置消息队列的属性
消息队列的属性保存在系统维护的数据结构msqid_ds中,用户可以通过函数msgctl获取或设置消息队列的属性。
#include<sys/msg.h>
int msgctl(int mspid, int cmd, struct msqid_ds *buf);
msgctl系统调用对msqid标识的消息队列执行cmd操作,系统定义了3种cmd操作:
·IPC_STAT:用来获取消息队列相应的msqid_ds数据结构,并将其保存在buf指向的地址空间中。
·IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes。
·IPC_RMID:从内核中删除msqid标识的消息队列。
例程演示如何获取消息队列的属性
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID	 32
#define PATH_NAME "."
void getmsgattr(int msggid, struct msqid_ds msg_info);
int main()
{
	/*User defined message buffer*/
	struct mymsgbuf
	{
		long msgtype;
		char ctrlstring[BUF_SIZE];
	}msgbuffer;
	int qid;
	int msglen;
	key_t msgkey;
	struct msqid_ds msg_attr;
	if(msgkey = ftok(PATH_NAME, PROJ_ID) == -1)
	{
		perror("msgget error!\n");
		exit(1);
	}
	
	if((qid = msgget(msgkey, IPC_CREAT|0660)) == -1)
	{
		perror("msgget error!\n");
		exit(1);
	}
	getmsgattr(qid, msg_attr);
	msgbuffer.msgtype = 2;
	strcpy(msgbuffer.ctrlstring, "Aother message");
	msglen = sizeof(struct mymsgbuf) - 4;
	if(msgsnd(qid, &msgbuffer, msglen, 0) == -1)
	{
		perror("msgget error!\n");
		exit(1);
	}
	getmsgattr(qid, msg_attr);
	msg_attr.msg_perm.uid = 3;
	msg_attr.msg_perm.gid = 2;
	if(msgctl(qid, IPC_SET, &msg_attr) == -1)
	{
		perror("msg set error!\n");
		exit(1);
	}
	getmsgattr(qid, msg_attr);
	if(msgctl(qid, IPC_RMID, NULL) == -1)
	{
		perror("delete msg error!\n");
		exit(1);
	}
	getmsgattr(qid, msg_attr);
	exit(0);
}
void getmsgattr(int msgid, struct msqid_ds msg_info)
{
	if(msgctl(msgid, IPC_STAT, &msg_info) == -1)
	{
		perror("msgctl error!\n");
			return;
	}
	printf("****information of message queue%d****\n", msgid);
	printf("last msgsend to msq time is %s\n", ctime(&(msg_info.msg_stime)));
	printf("last msgrcv time from msg is %s\n", ctime(&(msg_info.msg_rtime)));
	printf("last change msq time is %s\n", ctime(&(msg_info.msg_ctime)));
	printf("current number of bytes on queue is %d\n", msg_info.msg_cbytes);
	printf("number of meaasge in queue is %d\n", msg_info.msg_qnum);
	printf("max number of bytes on queue is %d\n", msg_info.msg_qbytes);
	printf("pid of last msgsnd is %d\n", msg_info.msg_lspid);
	printf("pid of last msgrcv is %d\n", msg_info.msg_lrpid);
	printf("msg uid is %d\n", msg_info.msg_perm.uid);
	printf("msg gid os %d\n", msg_info.msg_perm.gid);
	printf("*********information end!**********\n");
};
跑一下试试

结果片段显示的是对消息队列进行操作前的属性。发送消息以后和重新设置以后的消息队列属性都会因为操作而改变。
4.实例
/*server.c*/
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/stat.h>
#define BUF_SIZE	256
#define PROJ_ID		32
#define PATH_NAME	"/tmp"
#define SERVER_MSG	1
#define CLIENT_MSG	2
int main()
{
	struct mymsgbuf
	{
		long msgtype;
		char ctrlstring[BUF_SIZE];
	}msgbuffer;
	int	qid;
	int 	msglen;
	key_t	msgkey;
	if((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)
	{
		perror("ftok error!\n");
		exit(1);
	}
	if((qid = msgget(msgkey, IPC_CREAT|0660)) == -1)
	{
		perror("msgget error!\n");
		exit(1);
	}
	while(1)
	{
		printf("Server: ");
		fgets(msgbuffer.ctrlstring, BUF_SIZE, stdin);
		if(strncmp("exit", msgbuffer.ctrlstring, 4) == 0)
		{
			msgctl(qid, IPC_RMID, NULL);
			break;
		}
		msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1] = '\0';
		msgbuffer.msgtype = SERVER_MSG;
		if(msgsnd(qid, &msgbuffer, strlen(msgbuffer.ctrlstring)+1, 0) == -1)
		{
			perror("Server msgsnd error!\n");
			exit(1);
		}
		if(msgrcv(qid, &msgbuffer, BUF_SIZE, CLIENT_MSG, 0) == -1)
		{
			perror("Server msgrcv error!\n");
			exit(1);
		}
		printf("Client: %s\n", msgbuffer.ctrlstring);
	}
	exit(0);
}
/*client.c*/
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/stat.h>
#define BUF_SIZE	256
#define PROJ_ID		32
#define PATH_NAME	"/tmp"
#define SERVER_MSG	1
#define CLIENT_MSG	2
int main()
{
	struct mymsgbuf
	{
		long msgtype;
		char ctrlstring[BUF_SIZE];
	}msgbuffer;
	int	qid;
	int 	msglen;
	key_t	msgkey;
	if((msgkey = ftok(PATH_NAME, PROJ_ID)) == -1)
	{
		perror("ftok error!\n");
		exit(1);
	}
	if((qid = msgget(msgkey, IPC_CREAT|0660)) == -1)
	{
		perror("msgget error!\n");
		exit(1);
	}
	while(1)
	{
		if(msgrcv(qid, &msgbuffer, BUF_SIZE, SERVER_MSG, 0) == -1)
		{
			perror("Server msgrcv error!\n");
			exit(1);
		}
		printf("Server: %s\n", msgbuffer.ctrlstring);
		printf("Client:	");
		fgets(msgbuffer.ctrlstring, BUF_SIZE, stdin);
		if(strncmp("exit", msgbuffer.ctrlstring, 4) == 0)
		{
			break;
		}
		msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1] = '\0';
		msgbuffer.msgtype = CLIENT_MSG;
		if(msgsnd(qid, &msgbuffer, strlen(msgbuffer.ctrlstring)+1, 0) == -1)
		{
			perror("Client msgsnd error!\n");
			exit(1);
		}
	}
	exit(0);
}
运行一下

还行,但是写了这么多客户端服务器模型,我们已经发现,其消息不能随机发送,必须一来一回,谁必须先发是确定的(该模型客户端先发),因此,其还不够方便,后面将学习到更好的,满足通信随机性的服务器客户端模型。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号