Linux 利用消息队列进行进程通信

除了pipe之外,Linux进程通信还可以用消息队列完成。
主要用到的有以下几个函数:

msgget(key_t key,int msgflg) 用于创建或找到一个消息队列,返回该队列的id,这个函数需要两个参数,key类似于rand函数中的种子,一般使用ftok函数生成。msgflg参数用于描述该消息队列的控制权限。

ftok(char * filename,int id) 用于生成一个key值,在linux中利用文件的设备编号和节点的唯一性来生成唯一的key。

msgctl(int msqid, int cmd, struct msqid_ds *buf) 用于控制消息队列的状态,第一个参数是特定消息队列的id,第二个参数是要进行的操作,如IPC_RMID是用于移除该消息队列,还有一些其他的修改队列状态的参数,这时候就可以用到第三个参数,msqid_ds描述的是每个消息队列的状态。

msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) 用于向消息队列中发送消息,第二个参数是消息队列中每个元素的一个结构体,该结构体有两个元素,一个是储存的消息,一个是该消息的类型(来自于哪个进程)。第三个参数是该消息的长度。第四个参数是用于控制发送消息的方式,具体作用代码中有注释。

msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg) 用于从消息队列中接受消息,前两个参数和snd函数中一样,第三个参数是指定接受消息的长度,第四个参数是指定接受消息的类型,如果是0代表不论类型直接接受下一个消息,第五个指令与snd中相同。

利用上面的函数可以写出一个向队列写数据和一个接受数据的程序:
write:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define BUFFER_SIZE 1024

struct message{
    
    
	long msg_type;
	char msg_text[BUFFER_SIZE];
}; // 这个用于消息队列相关函数接受类型和字符串,类型用的是每个进程的编号,使用getpid函数
int main(){
    
    
	int qid; // 每个队列都会有的一个id
	key_t key; // 每个队列生成时候的一个值,类似于rand函数中的种子
	struct message msg;
	if((key = ftok("msg.txt",'a')) == -1){
    
     // ftok函数需要接受两个参数,第一个是文件名,第二个是一个0~255的子序号,使用这个函数只是为了让key值不冲突,key值其实可以自己设定。
		perror("ftok error"); // perror函数能在某个函数出现错误的时候将perror+错误信息打印出来。
		exit(-1);
	}
	if((qid = msgget(key,IPC_CREAT|0666)) == -1){
    
     // IPC_CREAT|xxxx是权限赋予的值
		perror("msgget error");
		exit(-1);
	}
	printf("Open queue %d\n",qid);
	while(1){
    
    
		printf("input:\n");
		if(fgets(msg.msg_text,BUFFER_SIZE,stdin) == NULL){
    
     // fgets函数可以用于从指定流中读入一个字符串(可带空格),且能指定缓冲区的大小,超过部分就不被接受。
			perror("fgets error");
			exit(-1);
		}
		msg.msg_type = getpid(); // 该msg的类型,用于多文件通信的时候指定类型接受。
		if((msgsnd(qid,(void *)&msg,strlen(msg.msg_text),0)) < 0){
    
     // 该函数向消息队列中写入一个message。带有类型和信息串。最后一个参数为0的时候意味着如果该消息队列已满,
																   // 那么该函数就会阻塞,直到可以写入,类似于pipe自带的那个阻塞机制。
																   // 除了0外,还有以下两个参数:IPC_NOWAIT 不等待直接返回。
																   // 						IPC_NOERROR 如果发送的消息大于size字节,就截断多余的部分。 
			perror("msgsnd error");
			exit(-1);
		}
		if(!strcmp(msg.msg_text,"quit\n")) // 如果不加\n,那么就会无法正常退出,因为fgets会接受\n这个转移字符,也可以使用strncmp函数避免这个问题的发生。
			break;
		else printf("%s", msg.msg_text);
	}
	if(msgctl(qid,IPC_RMID,NULL) < 0){
    
     // ipc_rmid指令用于杀死该消息队列
		perror("msgctl error");
		exit(-1);
	}
	exit(0);

	return 0;
}

message结构体就是消息队列中每个消息的结构体。

read函数:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define BUFFER_SIZE 1024

struct message{
    
    
	long msg_type;
	char msg_text[BUFFER_SIZE];
};
int main(){
    
    
	int qid;
	key_t key;
	struct message msg;
	if((key = ftok("msg.txt",'a')) == -1)
		exit(-1);
	if((qid = msgget(key,IPC_CREAT|0666)) == -1)
		exit(-1);
	printf("Open queue %d\n",qid);
	while(1){
    
    
		memset(msg.msg_text,0,BUFFER_SIZE);
		if(msgrcv(qid,(void *)&msg,BUFFER_SIZE,0,0) < 0){
    
     // 第四个参数用于指定接受的类型,如果是0的代表无论队列下一个消息是什么类型都要接受。第五个指令用于控制是否阻塞
			perror("msgrcv error");
			exit(-1);
		}
		printf("%s",msg.msg_text);
		if(msg.msg_text == "quit\n") // 这个可以省略
			break;
	}
	if(msgctl(qid,IPC_RMID,NULL) < 0){
    
    
		perror("msgctl error");
		exit(-1);
	}
	exit(0);

	return 0;
}

使用msgget函数的时候,如果这个key值产生的序列号是以前没有使用过的,就会创建一个新的消息队列,如果是使用过的就会继续沿用下去,完成通信的目的。

在read端的代码中其实可以省略掉最后break那段内容,因为当write端关闭掉消息队列的时候,在read端中msgrcv函数也会报错:msgrcv error: Invalid argument
这是因为队列已经被关闭了,如果继续访问这个队列的话是非法的操作,函数将会返回-1,并把错误信息存到error中。

猜你喜欢

转载自blog.csdn.net/weixin_45590210/article/details/115420048
今日推荐