进程的消息通信 & 消息队列小结

本文参考引用了以下大佬博客(万分感谢、佩服):

(https://blog.csdn.net/fengxinlinux/article/details/52673350)
(https://www.cnblogs.com/zhangxuan/p/6732552.html)
(https://www.cnblogs.com/kunhu/p/3608589.html)
(https://blog.csdn.net/poetteaes/article/details/80030114)


1.下面通过一个实验引入:

在这里插入图片描述

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

#define MSGKEY 75 

struct msgform
{ 
  long mtype;  
  char mtext[1030];
}msg;

int msgqid,i;

void CLIENT()
{ 
   int i;
   
   msgqid=msgget(MSGKEY,0777);
   
   for (i=10;i>=1;i--)
   {     
     msg.mtype=i;
     printf("(client)sent \n");
     msgsnd(msgqid,&msg,1024,0);  /*发送消息msg入msgid消息队列*/
   }
   
   exit(0);
}
void SERVER()
{
  msgqid=msgget(MSGKEY,0777|IPC_CREAT);  /*由关键字获得消息队列*/
  
  do
  {
    msgrcv(msgqid,&msg,1030,0,0);   /*从msgid消息队列接收消息msg*/
    printf("(server)received \n");
  }while(msg.mtype!=1);             /*消息类型为1时,释放队列*/
  
  msgctl(msgqid,IPC_RMID,0);
  
  exit(0);
}
void main()
{
   while((i=fork())==-1);//创建子进程
   if(!i) SERVER();//若子进程创建成功(i = 0),在子进程中运行SERVER
   
   while((i=fork())==-1);//创建子进程
   if(!i) CLIENT();//若子进程创建成功(i = 0),在子进程中运行CLIENT
   
   wait(0);//等待子进程结束
   wait(0);//等待子进程结束
}

在这里插入图片描述


2. 代码详解

2.1清楚两个概念

(1)IPC(Inter-Process Communication):进程间通信,是指两个或两个以上进程间相互通信,交换信息。

(2)消息队列:消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应唯一的键值,这个键的数据类型为key_t,通常在头文件<sys/type.h>中被定义为长整型,键值由内核转换为标识。内核中的消息队列是通过IPC的标识符来区别的,不同的消队列之间是相互独立的。要获得一个消息队列的描述符,只需要提供该消息队列的键值即可,该键值可以在公共头文件定义,也可以用ftok函数生成键值。

2.2 消息队列通信过程(五步)

(1)生成一个键值

 include <sys/msg.h>
 key_t ftok(const char *path, int id);   //成功返回键值 出错返回-1
path id
must refer to an existing, accessible file,在系统中一定要存在,且进程有访问权限 取值范围为1-255

(2)创建或者打开一个消息队列

 include <sys/msg.h>
 int msgqid = msgget(key_t key, int flag);

1.该函数如果调用成功返回一个消息队列的描述符,否则返回-1

2. 参数key即为ftok函数的返回值。flag是一个标志参数

flag取值
IPC_CREATE 表示创建一个新的IPC对象或者打开已有的IPC对象
IPC_EXCL 单独使用返回-1,没有什么实际意义
IPC_CREAT | IPC_EXCL 组合使用,用于创建一个新的IPC对象,如果键值对应的IPC对象已经存在则报错

(3)将数据放到消息队队列中

include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t msgsz, int msgflg); /*成功则返回0, 出错则返回-1*/

发送成功内核会更新与消息队列相关联的msqid_ds结构,指示调用者的进程ID以及调用时间,并且消息数加1。

常见错误码
EAGAIN 说明消息队列已满
EIDRM 单独使用返回-1,没有什么实际意义说明消息队列已被删除
EACCES 说明无权访问消息队列
msgid 函数向msgid标识的消息队列发送一个消息
ptr 指向发送的消息
msgsz 要发送消息的大小,不包含消息类型占用的4个字节
msgflg 操作标识位。可以设置为0或者IPC_NOWAIT。
如果为0,则当消息队列已满的时候,msgsnd将会阻塞,直到消息可写进消息队列;
如果msgflg 为IPC_NOWAIT,当消息队列已满的时候,msgsnd函数将不等待立即返回

1. ptr是指向一个长整型数,表示消息类型,紧接着是消息数据。如果消息数据长度最大512,可以定义消息结构如下:

struct mymsg
{
   long mytype;       /*正的长整型量,通过它来区分不同的消息数据类型*/
   char msgtext[512]; /*消息数据的内容,长度是nbytes*/
};

通过设定mtype 值,我们可以进行单个消息队列的多向通讯。比如,client 可以给它向server 发送的信息赋于一个特定的mtype 值,而server 向client 的信息则用另一个mtype值来标志。这样,通过mtype 值就可以区分这两向不同的数据。nbytes参数是mymsg结构中msgtext的长度,不是结构体的长度



2. msgqid_ds 结构被系统内核用来保存消息队列对象有关数据。内核中存在的每个消息队列对象系统都保存一个msgqid_ds 结构的数据存放该对象的各种信息。在Linux 的库文件linux/msg.h 中,它的定义是这样的:

/* one msqid structure for each queue on the system */

struct msqid_ds {

struct ipc_perm msg_perm;/*保存了消息队列的存取权限以及其他一些信息 */

struct msg *msg_first; /*保存了消息队列(链表)中第一个成员的地址 */

struct msg *msg_last; /*保存了消息队列中最后一个成员的地址 */

__kernel_time_t msg_stime; /* 保存了最近一次队列接受消息的时间 */

__kernel_time_t msg_rtime; /* 保存了最近一次从队列中取出消息的时间 */

__kernel_time_t msg_ctime; /* 保存了最近一次队列发生改动的时间 */

struct wait_queue *wwait; /* 指向系统内部等待队列的指针 */

struct wait_queue *rwait; /* 指向系统内部等待队列的指针 */

unsigned short msg_cbytes; /* 保存着队列总共占用内存的字节数 */

unsigned short msg_qnum; /* 保存着队列里保存的消息数目 */

unsigned short msg_qbytes; /* 保存着队列所占用内存的最大字节数 */

__kernel_ipc_pid_t msg_lspid; /* 保存着最近一次向队列发送消息的进程的pid */

__kernel_ipc_pid_t msg_lrpid; /* 保存着最近一次从队列中取出消息的进程的pid */

};



3. ipc_perm。系统中,每个消息队列都维护一个结构体,此结构体保存着消息队列当前状态信息。在Linux 的库文件linux/ipc.h中,它是这样定义的:

struct ipc_perm

{

key_t key;//IPC 对象的关键字

ushort uid; /* owner euid and egid */

ushort gid;

ushort cuid; /* creator euid and egid */

ushort cgid;

ushort mode; /* IPC 对象的存取权限 */

ushort seq; /* 系统保存的IPC 对象的使用频率信息 */

};


(4)从消息队列中读数据

include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); /*功成功则返回消息数据部分的长度, 出错则返回-1*/

读取成功内核会更新与消息队列相关联的msqid_ds结构,指示调用者的进程ID以及调用时间,并且消息数减1

msgid 消息队列描述符
ptr 读取的消息存储到msgp指向的消息结构中
msgsz 消息缓冲区的大小
msgtype 为请求读取的消息类型
msgflg 操作标志位。msgflg可以为IPC_NOWAIT, MSG_EXCEPT ,MSG_NOERROR,0:表示忽略
msgtype
msgtype==0 读取队列中的第一个消息
msgtype>0 获取具有相同消息类型的第一个信息
msgtype<0 获取类型等于或小于msgtype的绝对值的消息,如果有多个,返回类型值最小的消息
msgflg
IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时错误码为ENOMSG。
如果不指定这个参数,那么进程将被阻塞直到函数可以从队列中得到符合条件的消息为止。
如果一个client 正在等待消息的时候队列被删除,EIDRM 就会被返回如果进程在阻塞等待过程中收到了系统的中断信号,EINTR 就会被返回
MSG_EXCEPT 与msgtype配合使用,返回队列中第一个类型不为msgtype的消息
MSG_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将被丢弃。
如果不指定这个参数,E2BIG 将被返回,而消息则留在队列中不被取出。当消息从队列内取出后,相应的消息就从队列中删除了

(5)消息队列操作

 include <sys/msg.h>
 int msgctl(int msgid, int cmd, struct msgid_ds *buf);  

功能:查询消息队列描述符状态,或设置描述符状态,或删除描述符。成功返回0,失败返回-1

msgid 消息队列描述符
cmd 命令类型
buf 消息队列头结构 msgid_ds 指针
参数cmd
IPC_STAT 队列 id 的消息队列头结构读入 buf 中
IPC_SET 把 buf 所指向的信息复制到 id 的消息队列头结构中
IPC_RMID 删除 id 消息队列

猜你喜欢

转载自blog.csdn.net/weixin_42250302/article/details/89037144