进程间通信---管道和消息队列

进程间通信的目的:
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:对个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了什么事件
进程控制:有些进程希望完全控制另一个进程的执行(如:Debug进程)

进程间通信的发展:
管道:
System V进程间通信:
POSIX进程间通信:

进程间通信的分类:

管道:
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”,管道是Unix中最古老的进程间通信的方式。

  • 匿名管道:
函数原型:
int pipe(int fd[2]);
参数说明:
fd:文件描述符数组,fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码。

图解:

举一个例子:从键盘读取数据,写入管道,读取管道,写到屏幕。



管道读写规则:
当没有数据可读时:
  • read调用阻塞,即进程暂停执行,一直等到有数据来到为止,read调用返回-1,errno值为EAGAIN
当管道满的时候:
  • write调用阻塞,直到有进程读走数据,调用返回-1,errno值为EAGAIN。
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGIPE,进而可能导致write进程退出。
当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性
当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性

管道特点:
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信。比如,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可以应用该管道。
管道提供流式服务
进程退出,管道释放,所以管道的生命周期随进程。
内核会对管道操作进行同步与互斥
管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立两个管道

命名管道:
管道只能在具有共同祖先的进程间通信,在不相关的进程之间交换数据,可以使用FIFO文件来实现,被称为命名管道。命名管道是一种特殊类型的文件。

创建一个命名管道:

函数:
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
 参数说明:pathname:文件名   ,mode:文件的权限掩码,通过umask查看。

命令行:

$ mkfifo pathname
创建一个名为“my”的命名管道


命名管道的打开规则:
  • 如果当前打开操作是为读而打开FIFO时,阻塞直到有相应进程为写而打开该FIFO,立刻返回成功。
  • 如果当前打开操作是为写而打开FIFO时,阻塞直到有相应进程为读而打开该FIFO,立刻返回失败,错误码为ENXIO
匿名管道和命名管道的区别:
匿名管道由pipe函数创建打开。
命名管道由mkfifo函数创建,打开用open。
FIFO(命名管道)与pipe(匿名管道)之间唯一区别在于他们的创建于打开的方式不同,当这些工作完成之后,他们具有相同的语义。

消息队列:
消息队列提供了一个从一个进程向另外一个进程发送有类型数据块的方法,每个数据块都被认为是一个类型,接受者进程接收的数据块可以有不同的类型值。每个消息的最大长度是有上限的(MSGMAX),,每个消息队列的总字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。

消息队列的结构:在 /usr/include/linux/msg.h中


ipc对象数据结构 /usr/include/linux/ipc.h
内核为每个IPC对象维护一个数据结构。

__kernel_key_t : 其实是内置数据类型的一种重命名,用在内核中。 是一个int

消息队列在内核中的形式:

消息队列其实就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。

消息队列与管道相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序进行接收,而是可以根据自定义条件接收特定的消息。

可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列按照一定的规则添加一条消息,对消息队列有读权限的进程可以从消息队列中读取消息。


问题:怎么保证两个进程看到的是同一个消息队列?

如果给消息队列一个编号,这个编号让两个进程用某一种特定的方式获得,计算之后可以得到相同的数据,将该数据写入消息队列,此时作为该消息队列的编号。往后就可以保证他俩看到的是同一个消息队列。

消息队列的相关函数:

msgget函数:用来创建和访问一个消息队列

头文件:

#include <sys/types.h>

#include <sys/msg.h>

#include <sys/ipc.h>

函数原型:
   int msgget(key_t key, int msgflg);
参数:
    key:消息队列名字,键值,可设置成常数IPC_PRIVATE,或由ftok函数获取
    msgflg:标志位
返回值:成功返回一个非负数,是该消息队列的标识码;失败返回-1.

    标志位msflg的取值如下:

IPC_CREAT:创建新的消息 队列

IPC_EXCL:与IPC_CREAT一起使用,表示如果要创建的消息队列已经存在,则返回错误

IPC_NOWAIT:读写消息队列要求无法达到满足时,立即返回,不会出现堵塞

参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只是意味着即将参加新的消息队列。


ftok函数:将文件名转换成键值

头文件:

#include<sys/types.h>

#include<sys/ipc.h>

函数原型:
    key_t ftok(const char* pathname,int proj_id)
参数:
    pathname:文件路径名,这个文件必须是存在的并且是可以访问的
    proj_id:子序号,是一个8bits的整数,范围为0-255
返回值:
    成功返回与文件相对应的键值;失败返回-1


注意:
  • ftok是根据文件路径名,提取文件信息,再根据这些信息和proj_id合成key,该路径可以随便设置
  • 该路径是必须存在的,ftok只是根据文件inode在系统内的唯一性来取一个数值,和文件的权限无关
  • proj_id是可以自己约定,随意设置的。在Unix系统上,它的取值是1到255

key通过ftok函数获得,key在用户和内核层面上达成了共识。 进程给操作系统一个key,操作系统用这个key创建一个消息队列,另一个进程通过相同的方式也会生成同样的key,这样他们在运算期间后序就可以看到同样的消息队列。

key完成了进程间通信的第一个步骤,让两个进程看到同一份资源。


msgctl函数:消息队列控制函数

(当cmd为IPC_RMID时,作为消息队列删除函数)

函数原型:
  int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
   msqid:有msgget函数返回的标识码
   cmd:将要采取的动作(三个取值)
返回值:
    成功返回0,失败返回-1

cmd的取值如下:

  • IPC_STAT:把msqid_ds结构体中的数据设置为消息队列的当前关联值
  • IPC_SET:在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
  • IPC_RMID:删除消息队列

buf是执行msqid_ds结构的指针,它指向消息队列模式和访问权限的结构。msqid_ds结构中至少包括以下成员:

struct msqid_ds{
    uid_t uid;
    gid_t gid;
    mode_t mode;
 };

msgsnd函数:把一条消息添加到消息队列中

头文件:

 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>

函数原型:
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
    msqid:由msgget函数返回的消息队列标识码
    msgp:指向准备发送的消息的指针。指针msgp所指向的数据结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型
    msgsz:是msgp指向的消息长度,注意这个长度不包含消息类型的哪个long int长整型的长度
    msgflg:控制着当前消息队列满或者到达系统上限时将要发生的事情
    msgflg=IPC_NOWAIT表示队列满不等待,非阻塞,返回EAGAIN错误。
    msgflg=0 ,表示阻塞
返回值:成功,消息数据的一份副本将被放到消息队列中,并返回0,失败返回-1

说明:

1. 消息结构在两方面受到限制:

首先,它必须小于系统规定的上限值;

其次,它必须以一个long int长整型开始,接受者函数将利用这个长整型确定消息的类型    

2. 消息结构的参考形式如下:                                  

struct msgbuf{
    long int type;
    char text[1];
};


msgrcv函数:从一个消息队列接收函数

头文件:

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

函数原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数:
    msqid:由msgget函数返回的消息队列标识码
    msgp:指向准备接收消息的队列
    msgsz:msgp指向的消息长度,不包含保存消息类型的哪个long int
    msgtyp:可以实现接收优先级的简单形式。
            若msgtyp==0,则返回队列的第一条消息
            若msgtyp > 0,返回队列第一条类型等于msgtyp的消息
            若msgtyp < 0,返回队列第一条类型小于等于msgtyp绝对值的消息,并且是满足条件的消息类型最小的消息
    msgflg: 控制着队列中没有相应类型的消息可供接收时将要发生的事
            msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误
            msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
            msgflg=0表示阻塞
            msgtyp>0 且 msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息

一个进程间通信的例子:

客户端发送一条消息,服务器端接收从客户端发送的消息;服务器向客户端发送一条消息(应答),客户端接收消息。

comm.h

#ifndef _COMM_H_
#define _COMM_H_

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

#define PATHNAME "msg.tmp"  //用msg.tmp文件的索引结点号和Proj_id 来生成键值
#define Proj_id 0x20  //可以随意指定

#define SERVER_TYPE 1
#define CLIENT_TYPE 2

struct msgbuf{
	long mtype;
	char mtext[1024];
};

int CreateMsgQueue();
int GetMsgQueue();
int DestroyMsgQueue(int msqid);
int SendMsg(int msqid,int who,char *msg);
int RecvMsg(int msqid,int recvType,char out[]);

#endif

comm.c

#include "comm.h"
static int CommMsgQueue(int flags){
	key_t key = ftok(PATHNAME,Proj_id);
	if(key < 0){
		perror("ftok");
		return -1;
	}

	int msqid = msgget(key,flags);
	if(msqid < 0){
		perror("msgget");
		//失败返回-1
	}
	return msqid;
}

int CreateMsgQueue(){
	return CommMsgQueue(IPC_CREAT|IPC_EXCL|0666);
	//参数含义:创建消息队列,如果该消息队列存在,就返回错误,
	//权限为可读可写
}

int GetMsgQueue(){
	return CommMsgQueue(IPC_CREAT);
}

int DestroyMsgQueue(int msqid){
	if(msgctl(msqid,IPC_RMID,NULL) < 0){
		perror("msgctl");
		return -1;
	}
	return 0;
}

int SendMsg(int msqid,int who,char* msg){
	struct msgbuf buf;
	buf.mtype = who;
	strcpy(buf.mtext,msg);

	if(msgsnd(msqid,(void*)&buf,sizeof(buf.mtext),0) < 0){
		perror("msgsnd");
		return -1;
	}
	return 0;
}

int RecvMsg(int msqid,int recvType,char out[]){
	struct msgbuf buf;
	if(msgrcv(msqid,(void*)&buf,sizeof(buf.mtext),recvType,0) < 0){
		perror("msgrcv");
		return -1;
	}
	strcpy(out , buf.mtext);
	return 0;
}

server.c

#include "comm.h"

int main()
{
	int msqid = CreateMsgQueue();
	char buf[1024];
	while(1){
		buf[0] = 0;
		RecvMsg(msqid,CLIENT_TYPE,buf);
		printf("client: %s\n",buf);
		
		printf("Please Enter: ");
		fflush(stdout);
		//从标准输入中读,fd=0
		ssize_t read_size = read(0,buf,sizeof(buf)-1);
		if(read_size > 0){
			buf[read_size] = '\0';
			SendMsg(msqid,SERVER_TYPE,buf);
			printf("send done,wait recv...\n");
		}
	}
	DestroyMsgQueue(msqid);
	return 0;
}

client.c

#include "comm.h"

int main()
{
	int msqid = GetMsgQueue();
	char buf[1024];
	while(1){
		buf[0] = 0;
		printf("Please Enter# ");
		fflush(stdout);
		ssize_t read_size = read(0,buf,sizeof(buf)-1);
		if(read_size > 0){
			buf[read_size] = '\0';
			SendMsg(msqid,CLIENT_TYPE,buf);
			printf("send done,wait recv...\n");
		}
		RecvMsg(msqid,SERVER_TYPE,buf);
		printf("server# %s\n ",buf);
	}
	return 0;
}

Makefile

all:client server

client:client.c comm.c
	gcc -o $@ $^

server:server.c comm.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f client server

运行结果:客户端与服务器之间进行的通信。



ftok参考自:https://blog.csdn.net/u013485792/article/details/50764224

猜你喜欢

转载自blog.csdn.net/qiana_/article/details/79778657
今日推荐