公告

Gentoo交流群:838664909 欢迎您的加入

#1 2024-03-01 13:16:16

batsom
管理团队
注册时间: 2022-08-03
帖子: 607
个人网站

Gentoo 之 System V IPC+消息队列

System V消息队列是传统的Linux消息队列机制,它使用一组系统调用来创建、发送和接收消息。它的特点是可以在不同进程之间共享消息队列,但是在使用时需要手动管理消息队列的创建和删除。

优点:

    可以实现异步通信:发送进程将消息放入消息队列后即可继续执行,不需要等待接收进程的响应,接收进程可以在合适的时候去读取消息。
    支持多对多通信:多个进程可以同时向同一个消息队列发送消息,多个进程也可以同时从同一个消息队列接收消息。
    可以实现进程解耦:发送进程和接收进程之间通过消息队列通信,不需要直接的共享内存或者使用管道等方式,从而实现了进程解耦。
    消息队列中的消息可以按照优先级进行处理,这样可以实现一些特殊的消息处理逻辑。

缺点:

    消息队列是基于内核的,因此涉及到用户态和内核态的切换,可能会引入一定的性能开销。
    消息队列的容量有限,当消息队列满了之后,发送进程将无法再发送消息,接收进程也无法再接收消息,可能会引发消息丢失的问题。
    System V消息队列是Linux特有的IPC机制,因此在不同的操作系统上可能不具备可移植性。

多进程与多线程

使用有名管道实现双向通信时,由于读管道是阻塞读的,为了不让“读操作”阻塞“写操作”,使用了父子进程来多线操作,
1)父进程这条线:读管道1
2)子进程这条线:写管道2

    线程和进程都是并发运行的,但是线程和进程各自的使用的场合有所不同

        多线使用多线程更省计算机cpu和内存的开销

            创建出并发运行的线程目的-----------多线操作

    程序必须要去运行一个新程序时,此时必须涉及到多进程

        这里并发运行的主要目的并不是为了多线操作,而是为了单独的去执行新程序

        执行新程序时,我们只能使用多进程来操作,因为线程是不可能去执行一个新程序的

System V IPC

    无名管道和有名管道都是UNIX系统早期提供的比较原始的一种进程间通信方式



    后来Unix系统升级到第5版本时,又提供了三种新的IPC通信方式

        消息队列
        信号量
        共享内存

        System V就是系统第5版本的意思

        后来的Linux也继承了unix的这三个通信方式

管道的机制

    管道的本质就是一段缓存
    Linux OS内核是以文件的形式来管理管道

    我们都是使用文件描述符以文件的形式来操作:无名管道和有名管道

操作管道时:
除了pipe和mkfifo这两个函数外,其它的像read、write、open都是文件io函数


System V IPC

    System V IPC与管道有所不同:

    它完全使用了不同的实现机制,与文件没任何的关系

        也就是说内核不再以文件的形式来管理System V IPC

    对于System V IPC,OS内核提供了全新的API
    System V IPC时,不存在亲缘进程一说,任何进程之间都可以使用System V IPC来通信

System V IPC标识符

        这个“标识符”就是文件描述符的替代者,但是它是专门给System V IPC使用的
        不能使用文件IO函数来操作“标识符”,只能使用System V IPC的特有API才能操作

如何得到ipc标识符

调用某API创建好某个“通信结构”以后,API就会返回一个唯一的“标识符”

    比如创建好了一个“消息队列”后,创建的API就会返回一个唯一标识消息队列的“标识符”

ipc标识符作用

如果创建的是消息队列的话:

    进程通过消息队列唯一的标识符,就能找到创建好的“消息队列”

    使用这个消息队列,进程就能读写数据,从而实现进程间通信



    可以读写数据就是实现了通信

        也就是标识符就是可是识别传建好的system V IPC

消息队列
本质

消息队列的本质:由内核创建的用于存放消息的链表

    由于是存放消息的,所以把这个链表称为了消息队列

如何存放消息

消息队列这个链表有很多的节点,链表上的每一个节点就是一个消息

    注意是一个双向链表
    每个消息由两部分组成
        1)消息编号:识别消息用
        2)消息正文:真正的信息内容

发送接收消息过程
1.发送消息

(a)进程先封装一个消息包

(b)调用相应的API发送消息

这个消息包其实就是如下类型的一个结构体变量:
----封包时将消息编号和消息正文写到结构体的成员中


struct msgbuf{
				long mtype;         /* 放消息编号,必须> 0 */
				char mtext[msgsz];   /* 消息内容(消息正文) */
			};	

b过程:
1.调用API时通过“消息队列的标识符”找到对应的消息队列
2.将消息包发送给消息队列,消息包会被作为一个链表节点插入链表

2.接收消息

调用API接收消息时,必须传递两个重要的信息

    消息队列标识符
    你要接收消息的编号

    有了这两个信息:

    API就可以找到对应的消息队列,然后从消息队列中取出你所要编号的消息

        收到了别人所发送的信息,实现了通信

“消息队列”有点像信息公告牌:
--------发送信息的人把某编号的消息挂到公告牌上
--------接收消息的人自己到公告牌上去取对应编号的消息
如此,发送者和接受者之间就实现了通信

----使用消息队列实现网状交叉通信很容易

----消息队列作为媒介,一个往双向链表发数据,一个根据编号取出数据
消息队列使用步骤

    使用msgget函数

        消息队列不存在:创建新的消息队列

        消息队列存在:获取已存在的某个消息队列,并返回唯一标识消息队列的标识符(msqID)

            后续收发消息就是使用msqID这个标识符来实现的

    收发消息
        发送消息:使用msgsnd函数,利用消息队列标识符发送某编号的消息
        接收消息:使用msgrcv函数,利用消息队列标识符接收某编号的消息

    使用msgctl函数,利用消息队列标识符删除消息队列

    对于使用消息队列来通信的多个进程来说:
            只需要一个进程来创建消息队列就可以了
    对于其它要参与通信的进程来说:
            直接使用这个创建好的消息队列即可



    为了保证消息队列的创建,让每一个进程都包含创建消息队列的代码,谁先运行就由谁创建

    后运行的进程如果发现它想用的那个消息队列已经创建好了,就直接使用

    当众多进程共享操作同一个消息队列时,即可实现进程间的通信。

消息队列API

 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//功能:利用key值创建、或者获取一个消息队列
/*
1.如果key没有对应任何消息队列,那就创建一个新的消息队列
2.如果key已经对应了某个消息队列,说明消息队列已经存在了,那就获取这个消息队列来使用
*/
//msgflg:指定创建时的原始权限,比如0664
int msgget(key_t key, int msgflg);

key值--------用于为消息队列生成(计算出)唯一的消息队列ID

    第一种:指定为IPC_PRIVATE宏

        指定这个宏后,每次调用msgget时都会创建一个新的消息队列

    第二种:可以自己指定一个整形数,但是容易重复指定

    第三种:使用ftok函数来生成key

#include <sys/types.h>
#include <sys/ipc.h>
/*
ftok通过指定路径名和一个整形数,就可以计算并返回一个唯一对应的key值,
---------只要路径名和整形数不变,所对应的key值就唯一不变的
*/
//ftok只会使用整形数(proj_id)的低8位,因此我们往往会指定为一个ASCII码值
key_t ftok(const char *pathname, int proj_id);


    创建一个新的消息队列时,除了原始权限,还需要指定IPC_CREAT选项

    msgid = msgget(key, 0664|IPC_CREAT);
      创建一个新的消息队列,此时就会用到msgflg参数

多个进程如何共享同一个消息队列

    创建进程

        创建者使用"./file", 'a’生成一个key值
        然后调用msgget创建了一个消息队列

    key = ftok("./file", 'a');
    msgid = msgget(key, 0664|IPC_CREAT);

        当创建者得到msgid后,即可操作消息队列

    实现共享

只要能拿到别人创建好的消息队列的ID,即可共享操作同一个消息队列,实现进程间通信

    获取别人创建好的消息队列的ID,有两个方法:

    a)创建者把ID保存到某文件,共享进程读出ID即可

        这种情况下,共享进程根本不需要调用msgget函数来返回ID

    b)调用msgget获取已在消息队列的ID

            使用ftok函数,利用与创建者相同的“路径名”和8位整形数,生成相同的key值
            调用msgget函数,利用key找到别人创建好的消息队列,返回ID

        key = ftok("./file", 'a');
        msgid = msgget(key, 0664|IPC_CREAT);                       

    这种方法是最常用的方法,因为ftok所用到的“路径名”和“8位的整形数”比较好记忆

代码演示
创建一个消息队列

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MSG_FIFE "./msgfile"
int  create_or_get_msgque(){
    //创建消息队列,返回msgid
     int msgId=-1;
     key_t key=-1;
     int fd=0;
     //为了用到文件路径名,用open创建
     fd=open(MSG_FIFE,O_RDWR|O_CREAT,0664);
     //用生成的路径+asc码生成唯一整数
     key=ftok(MSG_FIFE,'a');
     msgId=msgget(key,0664|IPC_CREAT);
     return msgId;
}
int main(void){
    int msgID=-1;
    int ret=-1;
    msgID=create_or_get_msgque();
    /*父子进程收发消息*/
    ret=fork();
    if(ret>0){//父进程发送消息

    }
    else if(ret==0){//子进程接收消息

    }
    return 0;
}
 

转载:https://blog.csdn.net/weixin_47173597/article/details/127970145?spm=1001.2101.3001.6650.14&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-14-127970145-blog-9269561.235%5Ev43%5Epc_blog_bottom_relevance_base6&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-14-127970145-blog-9269561.235%5Ev43%5Epc_blog_bottom_relevance_base6&utm_relevant_index=24

离线

页脚

Powered by FluxBB

本站由XREA提供空间支持