【linux系统编程学习笔记】第五节:进程通信方式之IPC通信机制1(消息队列)

“我希望那个世界的自己,可以是一名旅行家,周游世界,记录世界各地的风景。”
I hope I can be a traveler, traveling around the world and recording the worldwide scenery.

进程通信方式之IPC通信机制(消息队列)

System-V IPC通信机制

程序构架流程:

IPC 对象的 key

相关API

key_t ftok(const char *pathname, int proj_id);

消息队列

int msgget(key_t key, int msgflg);

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

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)


 

System-V IPC通信机制

什么是IPC:

消息队列、共享内存和信号量被统称为system-VIPC,V是罗马数字5,是Unix的AT&T分支的其中一个版本,一般习惯称呼他们为IPC对象,这些对象的操作接口都比较类似,在系统中他们都使用一种叫做key的键值来唯一标识,而且他们都是“持续性”资源——即他们被创建之后,不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命令删除他们

  1. 内核当中为了增强进程与进程之间数据交互及效率的一种机制的对象

IPC里面有什么:

  1. 消息队列:(你可以完全的理解它是一个增强型的管道
    1. 具备管道的思想,可以往里面发送及读取数据
    2. 每一个数据都可以夹带类型
    3. 我们可以指定只读指定的类型的数据,其他数据,继续存放在这个消息队列中
  2. 共享内存:
    1. 速度最快的一种进程间通信方式
    2. 在内核空间(物理内存)中开辟一块内存出来,映射给不同的进程的虚拟内存中,这样我们就可在不同的进程中访问同一块内存
  3. 信号量:
    1. 操作某种资源的进程间的同步互斥操作体系,相当于一个增强型的全局变量(全局于不同进程中),用来代表一种资源,当资源没有的时候,我们会陷入睡眠。有如下3个特点
      1. 进程都可以访问这个变量
      2. 进程可以加减这个变量
      3. 当这个变量减到为0的时候,你再想去减它,他就让你进程陷入睡眠,等到这个变量的值你减的时候不会小于0,他才会让你进程继续往下面工作

程序构架流程

  1. 新建IPC对象,获取IPC的key
  2. 新建及初始化消息队列/共享内存/信号量对象
  3. 根据不同的对象开始操作
  4. 删除对象

 

IPC 对象的 key

跟文件类型,进程每次“打开”一个IPC对象,就会获得一个表征这个对象的ID,进而再使用这个ID来操作这个对象。IPC对象的,但是ID是可变的。key类似于文件的路径名,ID类似于文件的描述符。

系统中的多个进程,如果他们需要使用IPC对象来通信,那么他们必须持有这个对象的键值key:

【linux系统编程学习笔记】第五节:进程通信方式之IPC通信机制1(消息队列)_第1张图片

相关API

key_t ftok(const char *pathname, int proj_id);

#include  
#include   

key_t ftok(const char *pathname, int proj_id);

函数功能:

  • 获取一个IPC对象的key,方便我们在后期通过这一个key创建一个IPC对象的共享内存或者消息队列或者信号量

函数参数:

  • pathname:一个路径名,用来通过和一个路径与该函数的第二个参数去得到一个IPC的key
  • proj_id:这个是一个不为0的小于或等于255的数值,用来跟pathname组合得到IPC的key,用来区分相同路径下的不同的 key

返回值:

  • 成功返回一个IPC对象的key值,失败则返回-1,errno会被设置

消息队列

消息队列的使用方法一般是:

  1. 发送者:
    1. 获取消息队列的IDA)获取消息队列的ID
    2. 将数据放入一个附带有标识的特殊的结构体,发送给消息队列。
  2. 接收者:
    1. 获取消息队列的IDA)获取消息队列的ID
    2. 将指定标识的消息读出。

当发送者和接收者都不再使用消息队列时,及时删除它以释放系统资源。

int msgget(key_t key, int msgflg);

#include  
#include  
#include   

int msgget(key_t key, int msgflg);

函数功能:

  • 获取一个消息队列的对象的id,他可以创建及打开一个IPC对象的消息队列出来

函数参数:

  • key:IPC对象的key值
  • msgflg:消息队列操作标志位:
    1. IPC_CREAT:如果key值所对应的IPC对象中没有消息队列,则去创建一个消息队列出来
    2. IPC_EXCL:如果这个宏跟上面的创建标志并用,代表IPC对象存在消息队列则报错
    3. 在引用IPC_CREAT的基础上,我们可以或上去一个mode值,以代表创建的消息队列的权限,这个mode值其实就是open函数里面一样原理
    4. IPC_CREAT | 0666: 代表不存在就创建且设置权限为666,存在就打开
    5. IPC_CREAT | IPC_EXCL | 0666: 代表存在就报错,不存在就创建并打开

返回值:

  • 成功则返回消息队列的id,失败则返回-1,并且errno会被设置

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

#include  
#include  
#include   

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

函数功能:

  • 控制指定的消息队列(获取状态及设置信息)

函数参数:

  • msqid:指定要操作的消息队列的id
  • cmd:操作命令:
    1. IPC_STAT:获取消息队列的信息放到buf这个参数中,这个操作需要我们指定消息队列拥有读权限
    2. IPC_RMID:删除一个消息队列,其中该函数的第三个参数buf是忽略的,删除后会唤醒所有正在操作这个消息队列的进程,并且返回错误
    3. IPC_INFO:获取一个内核级别的消息队列的信息,这个信息是一个struct msginfo的结构体数据,里面包含声明了多个内核级别的消息队列的信息值
    4. MSG_INFO:linux中特有参数,用来获取消息队列的信息,获取的信息struct msginfo类型数据
    5. MSG_STAT:在linux中引用这个参数相当于引用IPC_STAT参数,获取一个msqid_ds结构体数据,引用这个参数的时候我们的msqid这个参数的意义就变了,并不是消息队列的id值,而是传一个内核消息队列的数组下标,所以这个参数对我们的使用意义不大

其中这些返回的结构体有以下内容:

struct msqid_ds {
   struct ipc_perm msg_perm;     /* 消息队列的权限,在下面会有说明 */
   time_t          msg_stime;    /* 发送数据的最新时间 */
   time_t          msg_rtime;    /* 接受数据的时间 */
   time_t          msg_ctime;    /* 最新更新时间 */
   unsigned long   __msg_cbytes; /* 当前队列当中的数据的字节数 */
   msgqnum_t       msg_qnum;     /* 当前队列当中的消息数 */
   msglen_t        msg_qbytes;   /* 在队列当中的最大的字节数 */
   pid_t           msg_lspid;    /* 发送数据的进程的pid */
   pid_t           msg_lrpid;    /* 接受数据的进程的pid是谁 */
};

struct ipc_perm {
   key_t          __key;       /* IPC对象的key值 */
   uid_t          uid;         /* 用户的ID */
   gid_t          gid;         /* 组ID */
   uid_t          cuid;        /* 创建的用户ID */
   gid_t          cgid;        /* 创建的组ID */
   unsigned short mode;        /* 权限值*/
   unsigned short __seq;       /* 消息队列的下标值,也就是序列号 */
};
 
struct msginfo {
    int msgpool; /* 系统消息总尺寸 */
    int msgmap;  /* 系统消息个数最大值 */
    int msgmax;  /* 系统单个消息尺寸最大值 */
    int msgmnb;  /* 吸入消息队列字节数最大值 */
    int msgmni;  /* 系统消息队列个数最大值 */
    int msgssz;  /* 消息段尺寸 */
    int msgtql;  /* 系统中所有消息队列中的消息总数最大值 */
    unsigned short int msgseg;
                   /* 分配给消息队列的数据段的最大值 */
};

返回值:

  • 成功则返回消息队列的id,失败则返回-1,并且errno会被设置

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

#include  
#include  
#include   

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

函数功能:

  • 向指定的消息队列,发送一个消息

函数参数:

  • msqid:你要将数据发送到哪个消息队列中,传入指定的消息队列的ID
  • msgp:消息结构体指针。
    1. 系统规定我们需要去定义各种结构体类型:
truct msgbuf {    
    /* 消息类型,必须是大于0的一个数字 */    
    long mtype;       
 
    /* 消息的内容,这个数组的大小是可以变化的,最大大小不能超过MAXMNB,也就是16k*/     
    char mtext[1];     
};
  •  msgsz:消息正文的长度,跟上面定义的mtext大小是一致的
  • msgflg发送数据的操作标志
    1. 0:以默认的模式进行发送数据,即:当缓冲区满了继续发会阻塞。
    2. IPC_NOWAIT:以非阻塞模式进行发送数据。即:当消息队列的缓冲区满了之后,继续发送不会阻塞,而是会立即出错返回。

返回值:

  1. 成功则返回0,失败则返回-1,errno会被设置

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)

#include  
#include  
#include   

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

函数功能:

  • 从指定的消息队列,接收一个消息

函数参数:

  • msqid:指定的消息队列的 ID。
  • msgp:消息结构体指针。
  • msgsz:消息结构体中存放正文的内存空间的最大值。
  • msgtyp:指定接收消息的标签(类型)。
  • msgflg:可选项。
    1. 0:以默认的模式进行接收数据,即:当消息队列中没有消息时会阻塞。
    2. IPC_NOWAIT:以非阻塞模式进行接收数据。即:当消息队列没有消息时,继续收不会阻塞,而是会立即出错返回。

返回值:

  • 成功则返回消息队列的id,失败则返回-1,并且errno会被设置

写端msg_write.c

#include 
#include 
#include 
#include 
#include 


//根据消息队列的要求,定义了一个消息结构体
typedef struct{
	long mtype;       /* 消息类型,这个值必须大于0 */
	char mtext[1024];    /* 消息数据,大小可变,最大不能超过16k */
}msgbuf_t;

int main(void)
{
	key_t key;
	int msg_id;
	int retval;

	///1、获取一个IPC对象
	key = ftok(".", 1);
	if(key == -1)
	{
		perror("获取IPC对象的key值出错");
		return -1;
	}

	//2、获取一个消息队列,key代表操作哪个IPC对象,IPC_CREAT代表如果消息队列不存在则创建,0644代表创建出来的权限
	msg_id = msgget( key, IPC_CREAT|0644);	
	if(msg_id == -1)
	{
		perror("创建或者打开消息队列出错");
		return -1;
	}

	msgbuf_t mymsg;//用自己定义的消息结构体类型定义一个消息变量

	mymsg.mtype = 250;//将消息的类型赋值为250
    //3、开始操作对象
	while(1)
	{
		//获取用户输入
		fgets(mymsg.mtext,sizeof(mymsg.mtext),stdin);
		/*发送消息进入指定的消息队列中
                msg_id:发送的消息队列是哪个
                mymsg:我们要发送的消息
                strlen(mymsg.mtext):发送的消息的文本大小
                0:代表正常操作消息队列
		*/
		retval = msgsnd( msg_id, &mymsg, strlen(mymsg.mtext), 0);
		if(retval == -1)
		{
			perror("发送消息失败");
			break;
		}
	}

	//4、删除消息队列
	msgctl( msg_id, IPC_RMID, NULL);

	return 0;
}

读端msg_write.c

#include 
#include 
#include 
#include 
#include 


//根据消息队列的要求,定义了一个消息结构体
typedef struct{
	long mtype;       /* 消息类型,这个值必须大于0 */
	char mtext[1024];    /* 消息数据,大小可变,最大不能超过16k */
}msgbuf_t;

int main(void)
{
	key_t key;
	int msg_id;
	int retval;

	//获取一个IPC对象
	key = ftok(".", 1);
	if(key == -1)
	{
		perror("获取IPC对象的key值出错\n");
		return -1;
	}

	//获取一个消息队列,key代表操作哪个IPC对象,IPC_CREAT代表如果消息队列不存在则创建,0644代表创建出来的权限
	msg_id = msgget( key, IPC_CREAT|0644);	
	if(msg_id == -1)
	{
		perror("创建或者打开消息队列出错\n");
		return -1;
	}

	msgbuf_t mymsg;//用自己定义的消息结构体类型定义一个消息变量
	struct msqid_ds remsg;//定义存放消息队列的信息结构体

	while(1)
	{
		msgctl(msg_id,IPC_STAT,&remsg);//获取消息队列的信息
		printf("当前队列当中的消息数:%lu\n",remsg.msg_qnum);
		printf("当前队列当中的数据的字节数 %ld\n",remsg.__msg_cbytes);
		
		/*
			发送消息进入指定的消息队列中
				msg_id:接收的消息队列是哪个
				mymsg:我们要接收的消息
				strlen(mymsg.mtext):接收的消息的文本大小
				250:对方的消息类型
				0:代表正常操作消息队列
		*/
		
		retval = msgrcv( msg_id, &mymsg, sizeof(mymsg.mtext), 250, 0);
		if(retval == -1)
		{
			perror("接受消息失败\n");
			break;
		}
		printf("读取到消息大小为%d,内容为 %s \n", retval, mymsg.mtext);
	}


	//删除消息队列
	msgctl( msg_id, IPC_RMID, NULL);

	return 0;
}

 

 

 

 

 

 

 

 

你可能感兴趣的:(Linux,系统编程)