百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

【Linux系统编程】进程间通信之消息队列

sinye56 2024-11-24 21:30 1 浏览 0 评论

01. 消息队列概述

消息队列提供了一种在两个不相关的进程之间传递数据的简单高效的方法,其特点如下:

1)消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取。

2)消息队列允许一个或多个进程向它写入或者读取消息。

3)与无名管道、命名管道一样,从消息队列中读出消息,消息队列中对应的数据都会被删除。

4)每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的。

5)消息队列是消息的链表,存放在内存中,由内核维护。只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在于系统中。

02. 消息队列相关函数

消息队列常用操作函数如下:

#include <sys/msg.h>

#include <sys/types.h>

#include <sys/ipc.h>


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

int msgget(key_t key, int msgflg);

int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

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

对于消息队列的操作,我们可以类比为这么一个过程:假如 A 有个东西要给 B,因为某些原因 A 不能当面直接给 B,这时候他们需要借助第三方托管(如银行),A 找到某个具体地址的建设银行,然后把东西放到某个保险柜里(如 1 号保险柜),对于 B 而言,要想成功取出 A 的东西,必须保证去同一地址的同一间银行取东西,而且只有 1 号保险柜的东西才是 A 给自己的。

对于上面的例子,涉及到几个比较重要的信息:地址、银行、保险柜号码。

只有同一个地址才能保证是同一个银行,只有是同一个银行双方才能借助它来托管,只有同一个保险柜号码才能保证是对方托管给自己的东西。

而在消息队列操作中,键(key)值相当于地址,消息队列描述符相当于具体的某个银行,消息类型相当于保险柜号码。

同一个键(key)值可以保证是同一个消息队列,同一个消息队列描述符才能保证不同的进程可以相互通信,同一个消息类型才能保证某个进程取出是对方的信息。

键(key)值

System V 提供的进程间通信机制需要一个 key 值,通过 key 值就可在系统内获得一个唯一的消息队列标识符。key 值可以是人为指定的,也可以通过 ftok() 函数获得。

#include <sys/types.h>

#include <sys/ipc.h>

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

功能:

获取键(key)值

参数:

pathname: 路径名

proj_id: 项目ID,非 0 整数(只有低 8 位有效)

返回值:

成功:key 值

失败:-1

创建消息队列函数

#include <sys/msg.h>

int msgget(key_t key, int msgflg);

功能:

创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的 key 值就能得到同一个消息队列的标识符。

参数:

key: ftok() 返回的 key 值

msgflg: 标识函数的行为及消息队列的权限,其取值如下:

IPC_CREAT:创建消息队列。

IPC_EXCL: 检测消息队列是否存在。

位或权限位:消息队列位或权限位后可以设置消息队列的访问权限,格式和open() 函数的 mode_t

一样(open() 的使用请点此链接),但可执行权限未使用。

返回值:

成功:消息队列的标识符

失败:-1

参考代码如下:

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#include <string.h>


int main(int argc, char *argv[])

{

key_t key;

int msgqid;


key = ftok(".", 2012); // key 值


// 创建消息队列

msgqid = msgget(key, IPC_CREAT|0666);


return 0;

}

测试结果:

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out

#查看消息队列

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ipcs -q

--------- 消息队列 -----------

键 msqid 拥有者 权限 已用字节数 消息

0xdc320009 0 deng 666 0 0

#删除消息队列

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ipcrm -q 0

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ipcs -q

--------- 消息队列 -----------

键 msqid 拥有者 权限 已用字节数 消息

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$

03. 消息队列读写操作

对于消息队列的读写,都是以消息类型为准。消息类型相当于保险柜号码,A 往 1 号保险柜放东西,对方想取出 A 的东西必须也是从 1 号保险柜里取。同理,某一进程往消息队列添加 a 类型的消息,别的进程要想取出这进程添加的信息也必须取 a 类型的消息。

在学习消息队列读写操作前,我们先学习消息队列的消息格式:

typedef struct _msg

{

long mtype; // 消息类型

char mtext[100]; // 消息正文

//…… …… // 消息的正文可以有多个成员

}MSG;

消息类型必须是长整型的,而且必须是结构体类型的第一个成员,类型下面是消息正文,正文可以有多个成员(正文成员可以是任意数据类型的)。至于这个结构体类型叫什么名字,里面成员叫什么名字,自行定义,没有明文规定。

发送消息

#include <sys/msg.h>

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

功能:

将新消息添加到消息队列。

参数:

msqid: 消息队列的标识符。

msgp: 待发送消息结构体的地址。

msgsz: 消息正文的字节数。

msgflg:函数的控制属性,其取值如下:

0:msgsnd() 调用阻塞直到条件满足为止。

IPC_NOWAIT: 若消息没有立即发送则调用该函数的进程会立即返回。

返回值:

成功:0

失败:-1

接收消息

#include <sys/msg.h>

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

功能:

从标识符为 msqid 的消息队列中接收一个消息。一旦接收消息成功,则消息在消息队列中被删除。

参数:

msqid:消息队列的标识符,代表要从哪个消息列中获取消息。

msgp: 存放消息结构体的地址。

msgsz:消息正文的字节数。

msgtyp:消息的类型。可以有以下几种类型:

msgtyp = 0:返回队列中的第一个消息。

msgtyp > 0:返回队列中消息类型为 msgtyp 的消息(常用)。

msgtyp < 0:返回队列中消息类型值小于或等于 msgtyp 绝对值的消息,如果这种消息有若干个,

则取类型值最小的消息。

注意:在获取某类型消息的时候,若队列中有多条此类型的消息,则获取最先添加的消息,即先进先出原则。

msgflg:函数的控制属性。其取值如下:

0:msgrcv() 调用阻塞直到接收消息成功为止。

MSG_NOERROR: 若返回的消息字节数比 nbytes 字节数多,则消息就会截短到 nbytes 字节,

且不通知消息发送进程。

IPC_NOWAIT: 调用进程会立即返回。若没有收到消息则立即返回 -1。

返回值:

成功:读取消息的长度

失败:-1

消息队列控制

#include <sys/msg.h>

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

功能:

对消息队列进行各种控制,如修改消息队列的属性,或删除消息消息队列。

参数:

msqid:消息队列的标识符。

cmd:函数功能的控制。其取值如下:

IPC_RMID:删除由 msqid 指示的消息队列,将它从系统中删除并破坏相关数据结构。

IPC_STAT:将 msqid 相关的数据结构中各个元素的当前值存入到由 buf 指向的结构中。

相对于,把消息队列的属性备份到 buf 里。

IPC_SET:将 msqid 相关的数据结构中的元素设置为由 buf 指向的结构中的对应值。

相当于,消息队列原来的属性值清空,再由 buf 来替换。

buf:msqid_ds 数据类型的地址,用来存放或更改消息队列的属性。

返回值:

成功:0

失败:-1

04. 测试代码

写消息队列代码

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#include <string.h>


typedef struct _msg

{

long mtype;

char mtext[50];

}MSG;


int main(int argc, char *argv[])

{

key_t key;

int msgqid;

MSG msg;


key = ftok("./", 2015); // key 值


// 创建消息队列

msgqid = msgget(key, IPC_CREAT|0666);

if(msgqid == -1)

{

perror("msgget");

exit(-1);

}


msg.mtype = 10; // 消息类型

strcpy(msg.mtext, "hello mike"); // 正文内容


/* 添加消息

msg_id:消息队列标识符

&msg:消息结构体地址

sizeof(msg)-sizeof(long):消息正文大小

0:习惯用0

*/

msgsnd(msgqid, &msg, sizeof(msg)-sizeof(long), 0);


return 0;

}

读消息队列代码

#include <stdio.h>

#include <unistd.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/msg.h>

#include <string.h>


typedef struct _msg

{

long mtype;

char mtext[50];

}MSG;


int main(int argc, char *argv[])

{

key_t key;

int msgqid;



key = ftok("./", 2015); // key 值


// 创建消息队列

msgqid = msgget(key, IPC_CREAT|0666);

if(msgqid == -1)

{

perror("msgget");

exit(-1);

}


MSG msg;

memset(&msg, 0, sizeof(msg));


/* 取出类型为 10 的消息

msg_id:消息队列标识符

&msg:消息结构体地址

sizeof(msg)-sizeof(long):消息正文大小

(long)10:消息的类型

0:习惯用0

*/

msgrcv(msgqid, &msg, sizeof(msg)-sizeof(long), (long)10, 0);

printf("msg.mtext=%s\n", msg.mtext);


// 把消息队列删除

// IPC_RMID:删除标志位

msgctl(msgqid, IPC_RMID, NULL);


return 0;

}

写端代码测试:

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ipcs -q

--------- 消息队列 -----------

键 msqid 拥有者 权限 已用字节数 消息

0xdf320009 32768 deng 666 56 1

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$

读端代码测试:

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ gcc 1.c

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ./a.out

msg.mtext=hello mike

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$ ipcs -q

--------- 消息队列 -----------

键 msqid 拥有者 权限 已用字节数 消息

deng@itcast:/mnt/hgfs/LinuxHome/code.bak2$

相关推荐

RHEL8和CentOS8怎么重启网络

本文主要讲解如何重启RHEL8或者CentOS8网络以及如何解决RHEL8和CentOS8系统的网络管理服务报错,当我们安装好RHEL8或者CentOS8,重启启动网络时,会出现以下报错:...

Linux 内、外网双网卡路由配置

1.路由信息的影响Linux系统中如果有多张网卡的情况下,如果路由信息配置不正确,...

Linux——centos7修改网卡名

修改网卡名这个操作可能平时用不太上,可作为了解。修改网卡默认名从ens33改成eth01.首先修改网卡配置文件名(建议将原配置文件进行备份)...

CentOS7下修改网卡名称为ethX的操作方法

?Linux操作系统的网卡设备的传统命名方式是eth0、eth1、eth2等,而CentOS7提供了不同的命名规则,默认是基于固件、拓扑、位置信息来分配。这样做的优点是命名全自动的、可预知的...

Linux 网卡名称enss33修改为eth0

一、CentOS修改/etc/sysconfig/grub文件(修改前先备份)为GRUB_CMDLINE_LINUX变量增加2个参数(net.ifnames=0biosdevname=0),修改完成...

CentOS下双网卡绑定,实现带宽飞速

方式一1.新建/etc/sysconfig/network-scripts/ifcfg-bond0文件DEVICE=bond0IPADDR=191.3.60.1NETMASK=255.255.2...

linux 双网卡双网段设置路由转发

背景网络情况linux双网卡:网卡A(ens3)和网卡B(...

Linux-VMware设置网卡保持激活

Linux系统只有在激活网卡的状态下才能去连接网络,进行网络通讯。修改配置文件(永久激活网卡)...

VMware虚拟机三种网络模式

01.VMware虚拟机三种网络模式由于linux目前很热门,越来越多的人在学习linux,但是买一台服务放家里来学习,实在是很浪费。那么如何解决这个问题?虚拟机软件是很好的选择,常用的虚拟机软件有v...

Rocky Linux 9/CentOS Stream 9修改网卡配置/自动修改主机名(实操)

推荐...

2023年最新版 linux克隆虚拟机 解决网卡uuid重复问题

问题描述1、克隆了虚拟机,两台虚拟机里面的ip以及网卡的uuid都是一样的2、ip好改,但是uuid如何改呢?解决问题1、每台主机应该保证网卡的UUID是唯一的,避免后面网络通信有问题...

Linux网卡的Vlan配置,你可能不了解的玩法

如果服务器上连的交换机端口已经预先设置了TRUNK,并允许特定的VLAN可以通过,那么服务器的网卡在配置时就必须指定所属的VLAN,否则就不通了,这种情形在虚拟化部署时较常见。例如在一个办公环境中,办...

Centos7 网卡绑定

1、切换到指定目录#备份网卡数据cd/etc/sysconfig/network-scriptscpifcfg-enp5s0f0ifcfg-enp5s0f0.bak...

Linux搭建nginx+keepalived 高可用(主备+双主模式)

一:keepalived简介反向代理及负载均衡参考:...

Linux下Route 路由指令使用详解

linuxroute命令用于显示和操作IP路由表。要实现两个不同子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。在Linux系统中,设置路由通常是为了解决以下问题:该...

取消回复欢迎 发表评论: