linux系统进程间通信方式(二):消息队列

简介

本文章继续介绍linux进程间通信的方式:消息队列。
消息队列也是system-V的IPC对象,它也是存在于内核中,有自己的ID;并且通过一个唯一的key来绑定它。

linux提供了一些api让我们使用这个IPC。下面通过一个示例来演示两个进程是如何通过消息队列来进行通信的。

1.设计意图

使用消息队列实现两个进程通信,一个进程往队列写数据,另一个从队列读取数据。

2.设计思路

1、首先有两个程序:p1.c、p2.c;

2、这两个程序先后运行之后系统会创建两个进程(下面统称为p1、p2);

3、p1首先申请一个消息队列,并创建两个信号量

4、p2打开消息队列、打开信号量

5、p1从键盘获取字符串,封装好一个消息,然后发送消息到队列

6、p2从队列读取消息,并打印出消息正文

7、通信过程中使用posix有名信号量实现同步(即p1写完队列p2才能读取队列)

代码实现

p1.c

#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>


#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>


struct msgbuf 
{
	long mtype;       /* message type, must be > 0 */
	char mtext[100];    /* message data */
};


int main(void)
{
	int msqid;	//保存消息队列的id(标识符)
	sem_t *sem1 = NULL;	//保存信号量地址
	sem_t *sem2 = NULL;	//保存信号量地址

	//首先申请一个消息队列
	msqid = msgget((key_t)123, IPC_CREAT|0666);	
	if(msqid < 0)
	{
		perror("creat a msg queue fail");
		return -1;
	}

	//创建或打开一个POSIX有名信号量
	sem1 = sem_open("sem1", O_CREAT|O_RDWR, 0666, 1);
	if(sem1 == SEM_FAILED)
	{
		perror("open sem1 fail");
		return -1;
	}
	
	//创建或打开一个POSIX有名信号量
	sem2 = sem_open("sem2", O_CREAT|O_RDWR, 0666, 0);
	if(sem2 == SEM_FAILED)
	{
		perror("open sem1 fail");
		return -1;
	}
	
	struct msgbuf msg_to_send;
	msg_to_send.mtype = 1;
	
	while(1)
	{
		//等待信号量有效,P操作
		sem_wait(sem1);

		if(fgets(msg_to_send.mtext, 11, stdin) != msg_to_send.mtext)
		{
			perror("get char from stdin fail");
			return -2;
		}
		
		if(msgsnd(msqid, &msg_to_send, 100, 0) < 0)
		{
			perror("send msg to queue fail");
			return -3;
		}
		
		//释放信号量,V操作
		sem_post(sem2);

		if(strncmp("quit", msg_to_send.mtext, 4)==0)
		{
			printf("p1 readys to quit\n");
			break;
		}
	}
	
	struct msqid_ds msg_ds;
	struct msginfo msg_info;

	msgctl(msqid, IPC_STAT, &msg_ds);
	printf("==%lu,%lu,%lu==\n", msg_ds.__msg_cbytes,msg_ds.msg_qnum,msg_ds.msg_qbytes);
	
	int array_id = msgctl(msqid, MSG_INFO, (struct msqid_ds *)&msg_info);
	printf("==%d,%d,%d,%d==\n", msg_info.msgpool,msg_info.msgmap,msg_info.msgtql,array_id);
	
	msgctl(array_id, MSG_STAT, &msg_ds);
	printf("==%lu,%lu,%lu==\n", msg_ds.__msg_cbytes,msg_ds.msg_qnum,msg_ds.msg_qbytes);
	

	sleep(2);
	
	//释放各种资源
	sem_close(sem1);
	sem_close(sem2);

	if(msgctl(msqid, IPC_RMID, NULL) < 0)
	{
		perror("remove msg queue fail");
	}

	return 0;
}

p2.c

#include <stdio.h>
#include <sys/types.h>
#include <string.h>

#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

struct msgbuf 
{
	long mtype;       /* message type, must be > 0 */
	char mtext[100];    /* message data */
};


int main(void)
{
	int msqid;	//保存消息队列的id(标识符)
	sem_t *sem1 = NULL;	//保存信号量地址
	sem_t *sem2 = NULL;	//保存信号量地址
	ssize_t rcv_bytes;	//保存从队列读取的字节数
	int ret;

	//首先申请一个消息队列
	msqid = msgget((key_t)123, IPC_CREAT|0666);	
	if(msqid < 0)
	{
		perror("creat a msg queue fail");
		return -1;
	}

	//创建或打开一个POSIX有名信号量
	sem1 = sem_open("sem1", O_CREAT|O_RDWR, 0666, 1);
	if(sem1 == SEM_FAILED)
	{
		perror("open sem1 fail");
		return -1;
	}
	
	//创建或打开一个POSIX有名信号量
	sem2 = sem_open("sem2", O_CREAT|O_RDWR, 0666, 0);
	if(sem2 == SEM_FAILED)
	{
		perror("open sem1 fail");
		return -1;
	}
	
	struct msgbuf msg_to_rcv;
	
	while(1)
	{
		//等待信号量有效,P操作
		sem_wait(sem2);
		
		ret = msgrcv(msqid, &msg_to_rcv, 100, 1, 0);
		if(ret < 0)
		{
			perror("receive msg from queue fail");
			return -3;
		}
		rcv_bytes = ret;
		printf("received a msg[%d bytes]:%s\n", (int)rcv_bytes, msg_to_rcv.mtext);
		
		sem_post(sem1);

		if(strncmp("quit", msg_to_rcv.mtext, 4)==0)
		{
			printf("p1 readys to quit\n");
			break;
		}
	}
	
	//释放各种资源

	return 0;
}

运行结果

p1进程:
1213

p2进程:
1234

延伸

  • 消息的类型是linux内核规定好的,不能更改;
  • 本示例中每次只发送一个消息,实际上可以一次发送多个消息;
  • 而且我们可以使用msgctl函数设置或者获取消息队列的状态或信息。

msgctl函数原型如下:

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

我们可以通过一些命令来获取IPC的状态、IPC的信息等。
这些状态或信息会放到struct msqid_ds这个结构体里面,

struct msqid_ds {
               struct ipc_perm msg_perm;     /* 拥有者和权限 */
               time_t          msg_stime;    /* 最近一次发送消息的时间*/
               time_t          msg_rtime;    /* 最近一次接收消息的时间 */
               time_t          msg_ctime;    /* 最近一次改动的时间,这里的改动是指IPC SET*/
               unsigned long   __msg_cbytes; /* 队列当前存在的字节数 (nonstandard) */
               msgqnum_t       msg_qnum;     /* 队列当前的消息数量 */
               msglen_t        msg_qbytes;   /* 队列允许的最大字节数 */
               pid_t           msg_lspid;    /* 最近一次发送消息的进程ID */
               pid_t           msg_lrpid;    /* 最近一次接收消息的进程ID */
           };

其中struct ipc_perm这个结构体有下列成员:

struct ipc_perm {
               key_t          __key;       /* Key supplied to msgget(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 */
           };

上面这个结构体我就不解释了,大家看英文理解可能更好(更合适)。

我们可以使用下面这些命令来操作消息队列,这里简要介绍一下:

IPC_STAT:这个命令可以获取指定的IPC的状态,所有状态信息通过上述的msqid_ds 结构返回。

IPC_SET:这个命令可以设置IPC的一些属性,比如:

msg_qbytes,
msg_perm.uid, 
msg_perm.gid,  
msg_perm.mode.

IPC_RMID:这个命令可以删除消息队列。

IPC_INFO:这个命令可以获取系统对消息队列的限制和参数,这些信息通过如下结构体返回(要注意强制类型转换为第三个参数的类型):

struct msginfo {
                      int msgpool; /* Size in kibibytes of buffer pool
                                      used to hold message data;
                                      unused within kernel */
                      int msgmap;  /* Maximum number of entries in message
                                      map; unused within kernel */
                      int msgmax;  /* Maximum number of bytes that can be
                                      written in a single message */
                      int msgmnb;  /* Maximum number of bytes that can be
                                      written to queue; used to initialize
                                      msg_qbytes during queue creation
                                      (msgget(2)) */
                      int msgmni;  /* Maximum number of message queues */
                      int msgssz;  /* Message segment size;
                                      unused within kernel */
                      int msgtql;  /* Maximum number of messages on all queues
                                      in system; unused within kernel */
                      unsigned short int msgseg;
                                   /* Maximum number of segments;
                                      unused within kernel */
                  };

MSG_INFO:这个命令可以获取消息队列消耗的系统资源信息,通过msginfo 结构返回(要注意强制类型转换为第三个参数的类型):

msgpool - 系统当前存在的消息队列的数量
msgmap - 系统当前所有消息队列的消息总数
msgtql - 系统所有消息队列所有消息的总字节数

MSG_STAT:这个命令可以获取关于所有消息队列的信息,信息通过msqid_ds结构体返回。

总结

本文详细介绍了消息队列的作用,如何通过他来实现进程间的通信;以及介绍了关于消息队列的一些操作。

posted @ 2022-05-05 21:33  李星云姬如雪  阅读(357)  评论(0编辑  收藏  举报