C语言笔记(24)System V IPC对象(上)

unix的官方版本中引入的一种,一大类进程间通信机制
(系统5)
在这里插入图片描述
IPC 主要包括3类 ,
共享内存 消息队列 信号灯集
IPC对象, linux中也都实现了, 也都可以用于我们进程间的通信
每个IPC对象有一个唯一的ID号 , 这个ID是一个数字, 就跟进程号类似,每一个进程都有一个唯一的pid
无论是共享内存 ,消息队列, 还是信号灯集, 都有一个于他对应的ID. 这个ID非常重要, 后面我们操作这个IPC对象就是通过这个ID来完成的 .这个ID是IPC对象在创建的时候由系统分配的 .分配的一个数字 ,

IPC对象共同点
它创建好以后(内核空间创建的)他就一直存在, 跟之前说的管道不一样 ,无论无名管道还是有名管道,当所有的读端还有写端都关闭的时候,内存中存放管道的内容会自动被释放 .而我们的IPC对象一旦创建好以后就一直存在 .直到我们通过函数或一些命令显式的把它删除, 或者是当我们系统关闭的时候这些IPC对象才会被释放 .
所以我们在使用IPC通信的时候,通常我们的程序中要主动的来删除, 最后一个进程来删除IPC对象
每一个IPC对象有一个和其相关联的一个KEY那么这个KEY可以看成是一个IPC对象的一个属性, 这个KEY值可以使得不同的进程能够找到, 或者能够打开同一个IPC对象.因为IPC对象创建好以后,他的ID是一个随机分配的,只有创建IPC对象的进程能够直接获的这个ID ,其他进程是不知道这个ID的,其他进程怎么样才能获得这个ID呢 ?就通过这个KEY ,因为每个IPC对象在创建的时候可以指定一个关联的KEY值,这个KEY值是我们事先约定好的. 创建IPC对象的进程把这个KEY值,和 这个对象关联起来, 其他进程就通过KEY值能够获取到这个对象的ID,所以如果是多个进程通过IPC对象来通信的话一定会用到通过这个KEY来打开同一个对象 .

除了通过函数可以访问之外, 还有两个相关的命令. ipcs / ipcrm
ipcs是用来查看系统5的IPC对象 , 那么ipcrm是用来删除
在这里插入图片描述
KEY值 0 表示什么含义 ?
表示这个对象是一个私有对象,
IPC对象用来在进程间通信的,它是可以让多个进程都能够去访问, 但是有时候,它是共享内存我可能只有我当前进程 ,我当前进程需要一个我私有的共享内存怎么办 >?
那我们创建的时候把KEY值指定成0 , 这样的话操作系统就知道你当前IPC对象是私有的那么这样的话就不会有其他进程能 够通过KEY值来获得你这个对象所对应的ID, 那么KEY值是0表示它是私有的, 如果KEY值为非0的话他实际上和我们的ID是一一对应的, 这样的话,其他进程就可以通过KEy值找到和这个KEY唯一对应的IPC对象ID,

ipcrm 用来 删除
在这里插入图片描述
在这里插入图片描述
进程在创建一个IPC对象之前,我们需要先指定一个Key,这个Key可以是为0, IPC_PRIVATE 就是0 ,为0的话表示这是一个私有对象,它只有我当前进程可以访问, 如果这个对象会被多个进程访问的话,那么我们一定要指定一个非零的Key值,这个Key值还可以通过一个函数ftok()来创建,然后根据我们的这个key值去创建一个共享内存,或者是消息队列,或者是信号灯集,然后根据他的id做相应的各种操作。我们更多的时候是例如IPC对象来实现进程间的通信,所以我们通常要指定一个非零的key值,那么这个key值我们如何来指定呢? 非零值
有没有可能我们的这个key和系统已存在的key值会冲突呢 ?
这是有可能的,为了降低KEY值冲突的可能性,系统提供了一个函数, 这个函数叫ftok这个函数是专门用来生成一个KEY值 , 我们看一下这个函数的用法,
在这里插入图片描述
返回值 :成功的时候返回的是一个合法的key值 , 失败时返回的是-1
第一个参数 : 是一个路径 , 要求这个路径所代表的文件 是我当前进程可以访问的并且是这个路径 存在的 ,为什么会有这个要求呢 ? 实际上ftok生成的key值它也是个数字, 只不过这个数字 怎么得到的呢 ? 那么这个数字是由我们第一个参数所指向的文件的 I(i) 节点的编号, 我们在文件系统中,每一个文件创建之后, 我们的文件系统会分配一个I(i)节点,而I(i)节点是有编号的, 是一个数字, I(i)节点的编号跟我们第二个参数的第八位进行一个组合. 进行一个移位把它拼成一个新的数字 ,那么这数字就是我们的地址 ,
第二个参数 : 是用来生成Key的这个数字有要求, 不能是0 , 并且只有第八位会参与生成key . 所以虽然是个整型, 但是我们一般传的话 我们的实参一般都传一个字符常量 , 因为字符常量有 ASCII 码值, 刚好是八位 ,
在这里插入图片描述
调用ftok 函数, 第一个参数是字符串所以用双引号, "."代表的是当前路径, 当前目录 ,后面工程号,我们传的是字符常量’a’ , ftok根据第一个参数指定的文件的I(i)节点编号跟第二个参数的第八位进行移位的组合得到一个key值, 这里需要注意 每个进程必须要生成相同的key,这样的话才能够根据相同的key找到相同的IPC对象 ,所以每个进程里面都应当调用这个ftok这个函数 ,并且它的参数必须是一致的 ,如果参数不一样的话,那么它得到的key值是不一样的, ,这样判断如果返回值等不等于 -1 ,说明这个函数的执行成功或失败 ,如果没有问题, 我们就可以利用Key值去创建 或者是去打开我们需要访问的IPC对象 ,

IPC对象第一种 : 共享内存
在这里插入图片描述
共享内存特点 :
效率最高的一种进程间通信方式, 为什么说效率高, 因为共享内存可以允许进程直接读写, 而不需要数据拷贝, 那么实际上无论是管道也好, IPC对象也好 , 他们创建的时候都是在内核空间创建的 ,
例如管道 : 去读写管道 实际上是需要数据在内核空间和用户空间之间交互, 需要内存拷贝, 很显然需要开销, 而共享内存运行我们的进程直接去访问,去读写共享内存, 而不需要内存拷贝, 不需要拷贝数据所以共享内存的效率是最高的,

共享内存创建好之后, 因为它是在内核空间创建所以我们的进程不能直接访问,我们还需要通过映射的方式把这块共享内存映射到进程的用户空间 才能够直接去读写 ,

那么同样因为多进程都可以同时访问共享内存, 所以我们在使用共享内存通信的时候, 通常还需要配合一些其他的一些同步 或者 互斥机制 来保证对共享内存的正确的访问 .
在这里插入图片描述
共享内存的使用步骤 :
第一步 : 首先创建 或是打开一个共享内存, 要指定一个key , 打开共享内存之后,根据共享内存的id我们需要把他映射, 把这个共享内存映射到我们进程的地址空间, 这样才能够去访问它,
第二步 : 接下来 我们就可以通过指针 来读写共享内存 , 那么在访问完共享内存之后 ,
第三步 : 我们还需要撤销映射 , 撤销共享内存映射 .
第四步 : 最后一定要记得去删除共享内存对象
因为如果不删除的话, 共享内存对象会一直存在,直到我们通过命令把它删除或者说系统结束,

如何创建和打开一个共享内存: ?
在这里插入图片描述
shmget 是用来创建或是打开一个共享内存对象,
返回值 : 整型 , 如果成功的话返回的是共享内存的id ,如果失败的话返回的是 -1
第一参数 : 就是和这个共享内存关联的key值 ,这个key值的话, 我们可以指定成 0 就是IPC_PRIVATE , 0 就表示我当前是要创建一个私有的共享内存对象 .那如果是多个进程, 通过共享内存通信的话, 那么这个key要指定成前面通过ftok 生成的key值, 那么每个进程里面都要指定相同的key值, 那么这样的话才能够 打开同一个共享内存对象,
第二个参数: 指定共享内存的大小 .是以字节为单位 .
第三个参数 : shmfig 指定的是共享内存的标志位 , 标志位的话 实际上是一个主要包含是否新建 以及对共享内存的读写权限 .主要包括这两部
在这里插入图片描述
如果传的参数是私有的话那么意味着这个共享内存一定是新建的所以第三个参数我们只需要指定权限,而不需要加上IPC_CREAT这个标志位,因为私有的共享内存一定是新建的
在这里插入图片描述
我们如何创建一个多个进程都能访问的共享内存 ?
第一步 : 我们首先要创建一个key , ftok生成, 相关的进程里面都需要调用ftok并且指定相同的参数, 这样才能得到一个相同的key .
第二步 : 我们得到key之后, 我们通过shmget ,第一个参数 就是和这个对象关联的key, 第二个参数就是共享内存大小 ,第三个参数我们指定的是IPC_CREAT|0666 如果我们这样指定的话代表 执行这个函数的时候系统会检查如果 和这个key关联的共享内存不存在 , 因为指定的是IPC_CREAT , 所以就会创建一个 共享内存对象. 并且把它和key值关联起来. 如果这个对象和这个key值关联的共享内存对象已经存在了 ,那么直接返回它的id …IPC_CREAT|0666 不存在就创建,如果存在就打开
多个进程通过共享内存 去通信的话, 每一个程序中都会 ,调用相同的代码, 执行相同的代码 ,那么第一个运行的进程它会去负责创建共享内存对象, 并且和这个key关联起来. 那么后面运行的进程, 实际上是根据key去找到这个对象, 然后返回它的id, 然后id保存在这个变量中, ,因为共享内存后面还有其他的操作都会通过这个id 来完成 .

在这里插入图片描述
共享内存映射 :
shmat 功能 : 根据共享内存的id ,把一个共享内存映射到我们的进程的地址空间 , 那么映射完之后我们就可以像我们自己分配的内存一样去访问共享内存 .
返回值 : 指针 .当函数执行成功之后 , 返回映射后的地址, 那么这个地址实际上是我们当前进程的地址空间的某个地址. 那么我们是可以去访问的 ,如果失败了返回的是一个特殊的值 -1 ,这个值也会强转成一个指针类型并返回
第一个参数 : shmid 要映射的共享内存的id ,前面一个函数 shmget的返回值 .
第二个参数 : shmaddr 它是一个指针, 就是一个地址, 这个地址是干什么用的呢 ? 我们可以指定映射后的地址, 如果我们没有指定这个地址 ,如果传的是NULL的话表示映射后的首地址由我们的操作系统帮我们来选择, 自动映射 ,直接把映射后的地址返回 .一般来说都是NULL , 因为你一个程序运行的时候哪些地址空间可以访问,实际上你在写程序的时候并不清楚 .所有的内存管理, 包括地址映射都是由操作系统来完成 ,所以这个地方我们通常指定成空指针 .由操作系统来帮我们映射, 我们只关心的是 , 映射后的首地址 .实际上跟我们之前C语言中用 malloc申请分配内存的用法是类似的 .那么我们分配一块内存的时候 实际上我们关心的是我们要所申请的大小 , 至于分配后的内存起始地址 , 具体是从哪个地址开始 ,实际上并不重要, 只要有这个地址就能够去访问 .
第三个参数 : 标志位 , 通常是指定读写方式, 0表示当前进程对映射后的共享内存可读可写 ,如果我们指定的是SHM_RDONLY 表示当前进程对共享内存 只有读的权限 .

一旦我们完成了这个映射, 映射之后我们就可以去访问共享内存 .怎么访问共享内存 ?
实际上是通过指针 , 在C语言中用 malloc分配一块内存之后, 这块内存我们怎么去使用.也是通过指针, 只要有这块内存的首地址 ,我们就可以通过相应的指针去访问 .通过指针去访问的时候,指针是要有具体的类型, 那么这时候我们就需要根据共享内存中存放的数据类型, 来定义相同类型的指针 .
比方说,如果我的共享内存我打算 往共享内存中放的都是字符串的话, 那么它访问的对象就是字符对象 那么这时候我就应当定义一个 字符指针去访问 ., 同样如果我想在共享内存中存放都是结构体的话 ,那么我就应当定义一个结构体指针 ,来访问这块内存 . 访问是非常灵活的 .
在C中访问一块内存的话一定要非常灵活, 很灵活的根据我内存中数据的格式来定义 ,换句话说就是内存申请之后,程序想怎么用都可以 .

在这里插入图片描述
如何往一个共享内存中 写入一个字符串 ?
共享内存中存放字符串, 那么就知道存放的数据类型就是char字符型 , 这时候我们就应当定义一个字符指针来操作这一块共享内存 ,假设我们 已经通过相应的函数 ,已经获取了一块共享内存 ,下面我们进行映射。第一个参数 :共享内存的id 。第二个参数 : NULL表示由操作系统帮我们完成选择一个合适地址来映射, 并且返回这个地址 ,第三个参数 : 0 表示我们对这个共享内存 可读可写 ,返回值是一个要赋值给char * addr 这个变量 所以我们要做一个强转char * 再判断返回值如果等于-1表示映射失败了,
如果映射没有问题, 我们就需要往共享内存中用键盘输入字符串, 我们利用fgets()第一个参数就是共享内存的首地址,就是存放要输入内存的缓冲区的首地址 ,可以是我们定义的一个字符数组, 也可以是我们映射后内存的起始地址。这个N的话是我们共享内存创建的时候指定的大小,防止越界 。stdin代表的是标准输入 。

共享内存映射完之后,我们的进程就可以去访问它, 那么如果我们不需要访问 共享内存,那我们应当通过一个函数来撤销这个映射 。向系统表明这块共享内存从地址空间中取消映射,
在这里插入图片描述
shmdt 取消共享内存的映射 :
返回值 : 整型 , 0 表示执行成功, 如果失败返回-1
参数 : 就是我们之前映射后的首地址 shmat 映射后的返回值 ,它就是我们映射后的首地址,所以我们需要把这个首地址保存下来,这样我们取消映射的时候需要用它作为参数 ,就跟 malloc申请后的首地址需要保存下来,free的时候用到,是一样的
如果进程中不再需要使用一个共享内存的时候就应当把它 及时的撤销映射 ,如果我们程序中没有调用这个函数撤销映射的话,当我们进程结束的时候我们操作系统会自动帮我们去撤销, 所有共享内存的映射。 当然有些程序的话比如说我们的服务器程序,它实际上是一直在后台运行,不会结束,那么如果我们在服务器程序中我们映射了共享内存,而没有取消映射的话,实际上这个共享内存是无法被删除的。 所以只要不需要访问了就要及时的取消映射 。
在这里插入图片描述
共享内存控制 :
shmctl 用来对共享内存做各种控制 ,设置,
返回值 : 整型, 成功返回0,失败的时候返回-1
第一个参数 : shmid要操作的共享内存的id ,
第二个参数 : cmd (command)命令字也可以叫操作, 含义是 :就是指定我们对这个共享内存要执行什么样的操作, 因为这个函数可以对共享内存进行多种操作。 比如获取它的属性, 比如去设置, 删除, 具体的参数就是通过这个参数来指定, 在我们的系统中每一个操作都有对应的宏, 叫操作码或者命令字,。三种最常用的操作,这都是我们系统中定义好的宏,这些宏就代表不同的操作,IPC_STAT 获取当前共享内存的属性 ,包括共享内存的大小,跟它关联的key指,共享内存的权限,包括它的创建者用户id等等,这些属性都能够获取。那么执行这个操作的时候,它会获取共享内存的属性,那共享内存的属性存放在什么地方呢 ?第三个参数 。
IPC_SET用来设置一个共享内存的属性,它会把第三个参数存放在第三个参数地址中的属性设置到我们指定的共享内存中。IPC_RMID 用来删除一个共享内存的ID,如果是删除一个共享内存的id的话,实际上第三个参数是用不到的, 所以第三个参数直接传NULL就可以了,。
第三个参数 : 用来保存或者设置共享内存的属性的值 。 结构体指针 ,那么如果我们指定的是这个IPC_STAT这个操作的话那么当前这个共享内存的属性就会保存到这个参数结构体指针所指向的结构体中, 这个参数怎么传呢 ? 就是定义一个结构体变量,传它的地址进来。

共享内存在进程间通信里用的比较多, 同样共享内存在使用的时候,
注意事项 :
共享内存实际上我们在创建共享内存的时候要指定一块大小,那么共享内存的大小是有限制的。你不能申请任意大小的共享内存, 在Linux系统中对共享内存的大小,以及我们系统中使用的所有共享内存的总大小都是有一个限制的, 那么我们可以通过一个命令来查看, 这个命令还是我们之前介绍过的叫 ipcs -I 查看一些系统的一些设定。 这里面就能够详细的列出我们当前系统中关于三类IPC对象的一些设置,
在这里插入图片描述
如果我们要申请一个超过32k的共享内存,很显然系统当前的设定就无法满足我们的需求,我们在创建的时候就会失败,我们如何来改这个设定呢?
cat /proc/sys/kernel/shmmax 这里面存放的就是我们当前系统中对于共享内存最大大小的设定, 我们可以用一些方法设定(爱寇)把一个我们希望的值写进去,也可以通过一个命令叫(谁四啃戳)用来设定我们的内核中的一些参数。
这是我们在使用共享内存的时候,需要注意的。
在这里插入图片描述
删除共享内存 ?
什么时候去删除?
什么时候共享内存真正的被删除 ?
共享内存的话,它通常用于多个进程间通信 ,那么共享内存一般谁来创建呢 ?很显然是第一个进程, 首先运行的进程应当负责来创建共享内存, 那么谁去删除呢 ?一般来说是由最后一个进程负责删除共享内存 。
shmctl(shmid,IPC_RMID,NULL)添加删除标记
实际上执行这个函数的时候,并没有立刻删除这个共享内存, 它仅仅是把这个共享内存标记成 要删除。那么什么时候会真正删除呢 ?只有当所有的进程都取消, 对某一个共享内存的映射,并且它已经被标记为要删除的时候,共享内存才真正的被删除, 也就是说我们的系统会检查每个共享内存里有一个nattach这个属性, nattach记录了有几个进程映射了这个共享内存 ,一个进程里面调用了一次 shmdt那么这个值就会加1,当某一个进程把共享内存标记成要删除的时候,我们的这个内核会检查,如果当前共享内存的nattach的值不为0,表明当前还有其他进程正在映射了这个共享内存,这时候共享内存是不会被删除的, 直到什么时候呢?直到所有的相关进程都执行了shmdt取消了映射,或者说相关进程都结束了,因为进程结束了会取消映射。当nattach变成0的时候才会真正的去删除, 这一点跟我们后面介绍的消息队列和信号灯是不一样的, 后面系统5的信号灯和消息队列它是立刻删除 ,而共享内存是当所有的映射都取消的时候,才会删除。这一点在使用的时候注意 。
在这里插入图片描述
消息队列 :
在应用开发中使用非常普遍 ,进程间通信的机制 , 对于消息队列来说, 每一个消息队列有一个唯一的id ,通过这个id能够指定向某一个消息队列发送消息, 也可以从这个消息队列中接收消息 .就是存放 一个消息的列表 , 一个消息队列中可以存放很多不同的消息, 我们的这个进程可以根据需要向消息队列中发送消息, 也可以从消息队列中接收消息 . 并且在回收的时候还能够指定 不同的方式去接收. 消息队列的最大特点就是支持不同的类型 ,也就是说消息队列中的消息,它是按照不同的类型来存放的. 这样的一个好处就是你不同的进程能够很方便的通过一个消息队列来实现一个 向某一个具体的进程发送消息进行通信 , 比方说我有多个进程通过一个消息队列通信的话,那么我们就可以在这个消息队列中为每一个进程, 给它定义一个消息,不同的消息类型. 那么这样的话每个进程, 如果你想和其他进程通信的话, 你就可以发送对方的类型的消息 . 那么同样如果对方要和你通信的话,它会发送你所对应的类型的消息, 而你只需要接收自己的类型, 发送对方的类型, 这样就可以在任意两个进程之间或者是一个进程或者多个进程之间能够很方便的进行一个通信 .

在这里插入图片描述
当我们创建好一个消息队列之后, 在内核中就创建了一个结构体 ,来描述这样消息队列 . 存放了消息队列的属性以及当前消息队列中的所有的消息, 属性包括,比如说消息队列的大小 ,消息队列中当前消息的个数,消息队列的权限,它所关联的key值 ,包括消息队列中第一消息 以及最后一个消息的接收的时间等等 .都存放在这个结构体中 , 我们还可以看到在这个消息队列中不同类型的消息是分别存储的, 这里面我们可以看到 ,这里面对每种类型的消息, 我们消息队列中会有一个链表, 链式的队列来存放, 那么当我们的消息队列收到某一个消息的时候, 会根据消息的类型把它存放到跟这个类型所对应的链表中,当我们的消息队列收到一个新的类型的时候 那么会创建一个新的链表来存放对应的这个类型的消息. 在每个链表中记录了当前这种类型消息的个数, 先进先出操作方式.
在这里插入图片描述
消息队列的使用步骤 :

第一步 : 打开或者创建一个消息队列 megget , 获得消息队列的id之后,
第二步 :  我们就可以向消息队列发送消息 msgsnd , 发送消息的时候我们还可以指定消息的类型,		       			    
第三步 : 同 样我们也可以从消息队列中指定,接收我们指定类型的消息msgrcv
第四步 : 最后我们还 可以对消息队列进行各种控制.  msgctl  包括获取消息 , 或者是设置消息队列的属性包括删除消息队列等都可以通过msgctl 来完成 .

如何创建打开一个消息队列 :
在这里插入图片描述
msgget 功能 :创建或打开一个消息队列 ,
返回值 :整型 .成功的时候,返回的是消息队列的id ,返回值我们需要把他保存下来, 因为我们后面操作消息队列都是通过id 来完成. 如果出错的话,返回-1
第一个参数 : key 就是和消息队列所关联的key值,所关联的键值 , 如果我们这里是想创建一个私有的消息队列 , 那么key值可以指定成 IPC_PRIVATE , 那么如果是多个进程,都 要访问同一个消息队列, 那么这里的key值需要通过ftok 来创建, 这样的话 多个进程都指向相同的 key值,从而能够打开同一个消息队列进程通信 .
第二个参数 : msgfig 是一个标志位 , 那么这个标志位的话我们一般就指定成 IPC_CREAT | 0666
这样指定有什么含义 ? 执行这个函数的时候加上IPC_CREAT的话 , 在队列不存在的时候 ,就会新建,那么执行这个函数的时候, 我们的系统会检查如果和这个key值所关联的消息队列不存在, 如果我们指定的IPC_CREAT 这时候就会创建一个消息队列,返回它的id, 如果和它这个key值相关联的消息队列已经存在了, 那么直接返回这个 消息队列的id(会检查权限) 一般我们都指定成IPC_CREAT | 0666
在这里插入图片描述
当前目录是个相对路径所以每个进程在调用ftok的时候,如果都指定成当前目录"."那么必须在同一个目录下运行,这样才能确保当前目录是同一个目录文件,第二个参数指定工程号, 先得到一个key,然后在调用msgget函数.
这样我们就获得了一个消息队列的id , 接下来我们就可以发送消息 .

如何发送消息 :
在这里插入图片描述
msgsnd : 发送消息 .
返回值 : 整型 ,如果成功返回 0 失败返回-1
第一个参数 : 消息队列的id ,
第二个参数 : 指针 , 地址 , 这个地址存放的就是我们要发送的消息 .消息缓冲区的地址 .所以我们首先要构造好一个消息放到这个地址 .然后通过这个函数来发送 .
第三个参数 : size这个参数是指定我这次要发送消息的长度 , 这个长度是消息中正文的长度, 是不包含消息的类型 .
第四个参数 : 标志位 ,发送的方式 ,0 或 IPC_NOWAIT , 0表示消息发送成功了才返回 , IPC_NOWAIT不管消息发送有没有成功, 不需要等待 .不需要等待完全成功就能够返回 .
0 的话, 我们往消息队列中发送消息的时候一般来说都会成功并立刻返回 .有一种情况例外 ,就是消息队列满了,没有空间了 ,那么这时候我们再发送消息的时候 . 如果是0就会阻塞 .阻塞到消息队列有空间,消息发送成功为止 才返回 .而如果是IPC_NOWAIT 那么如果队列有空间 那么发送成功返回 0 ,如果当前队列中没有空间的话 , 那么不会阻塞,会立刻返回-1,并且设置错误信息. 表示当前没有资源
0 阻塞方式, 成功才返回
IPC_NOWAIT 非阻塞方式, 立刻返回,是否成功
在这里插入图片描述
消息格式:
在这里插入图片描述
一般都是通过结构体来定义, 结构体定义非常灵活, 每个结构体中我们可以定义有不同类型的成员, 可以有多个成员 .每个成员有不同的类型,
定义消息的结构体中 第一个成员是固定的
第一个成员类型 : 必须是long 代表的是我当前的消息类型,必须是一个正整数 不能是0或者负数,具体每个进程是什么类型,自己约定好如果是两个进程的话就只需要两个类型 , 除了第一个成员之外, 其他成员都属于正文. 其他成员可以根据我们的需要自己来指定 .可以指定任意个成员, 每个成员可以有不同的类型,

一般来说最简单的消息的话,至少包含两个成员, 第一个成员代表消息类型,第二个成员代表消息的类型.
在这里插入图片描述

接收消息 - msgrcv
返回值 : 整型返回值 , 执行成功的时候,返回的是收到的消息的长度, 它是一个大于0的数, 失败了会返回 -1 .
第一个参数 : msgid 指定要操作消息队列的id
第二个参数: 是个指针, 地址 ,(存放我们接收到的消息)存放的是我们消息缓冲区的地址., 也就是我们接收到的消息,就会存放到这个地址, 一般我们在传实参的时候先定义一个消息结构体变量, 然后,把它的地址传进来就可以.
第三个参数 : 我们指定要接收的消息的长度, 如果我们指定的长度比实际的消息的长度要短的话 ,那么会把这个实际的消息按照我们指定的大小 会进行截取 .也就是说超出我们指定长度的这个部分信息会丢失 . 如果指定的size超过了消息的长度 ,这个没有问题, 所以为了避免出现消息被截断的情况, 我们一般是约定好,每一次发送和接收 ,消息的长度是固定的.
第四个参数 : 指定接收消息的类型 ., 这一点跟我们消息的发送不一样 ,我们在发送消息的时候,我们参数中不需要指定类型 , 消息类型是存放在结构体的第一个成员里 , 而接收一个消息的时候,我们在函数的参数中要指定接收消息的类型 .那么接收消息类型的话 一般来说,我们是指定一种具体的类型 ,比如说100 或 200 那么只接收指定类型的消息 . 还有一种就是可以指定成 0 ,那么如果指定成零代表什么含义呢 ? 表示是接收这个最早的消息, 指定成0的话,实际上就不是按照类型接收了, 而是按照消息的先后 接收消息队列中, 最早的一个消息 ,具体是接收类型还是按时间接收. 根据应用需求 .
还有一种用法就是指定成一个负数. 指定成负数的话 ,实际上是按照一个优先级去接收, 这个用法的话并不常见(一般这个类型应当是当前进程自己的类型 , 多个进程通过消息队列通信的时候,怎么样能够区分哪一个消息应当给哪一个进程呢 ? 就通过不同的类型. 比如说 : 两个进程那么我们可以指定成两个类型一个是100 一个是200 那100这个进程它要发消息给这个200这个进程的话, 它应当发送的是对方的类型 , 那么同样接收的时候接收的是自己的类型)
第五个参数 : 标志位, 表示我按照什么方式,如何去接收一个消息 一般来说这个标志位, 要么是 0 , 要么是IPC_NOWAIT ,如果是0的话 ,我们在接收一个指定类型消息的时候有两种情况,一种是当前有消息, 一种是没有消息. 如果有消息 不用说,就是能够接收到消息并且返回消息的长度, 如果当前队列中没有我们指定类型的消息, 0就表示进程会阻塞, 会在这个 msgrcv函数阻塞 ,一直阻塞直到要么是有消息了, 要么是消息队列被删除了出错了返回, 要么是被信号打断了.
如果是IPC_NOWAIT , 就是如果有消息就立刻接收并返回, 如果没有消息就不阻塞, 不等待. 立刻返回一个错误,告诉进程没有消息 ,一般来说我们更多的还是习惯使用 0 ,那么没有消息的时候,就一直阻塞等待,直到有消息为止,

在这里插入图片描述

控制消息队列 :
msgctl
返回值 :整型 .成功返回 0 ,失败返回-1
第一个参数 : 要设置的消息队列的id,
第二个参数 : 命令字表示要执行的操作, IPC_STAT 来获取消息队列的属性 , IPC_SET 用来设置消息队列的属性 , IPC_RMID 用来删除消息队列. 前两个操作的话都会用到第三个参数 .删除消息队列的话第三个参数用不到 ,我们直接传NULL就可以 .
第三个参数 : 用来存放队列属性的地址 , 如果删除的话,不需要第三个参数 ., 一般来说第一个进程来创建消息队列, 最后一个进程负责删除消息队列 , 那么消息队列删除的时候, 跟共享内存不一样 .共享内存系统会检查所以进程都取消映射了才会真正删除. 而消息队列只要我进程一执行 IPC_RMID那么这个消息队列会立刻被删除 ,而不管有没有进程正在发送或接收, 如果消息队列被删除了,那么进程去发送和接收 ,会立刻出错.
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那程序怎么退出呢 ? 如果用Ctrl + c 退出的话, 消息队列是不会删除的 , 如果用Ctrl + c退出的话, 我们得用命令把消息队列删除
在这里插入图片描述
如果用Ctrl +c的话不方便, 我们需要完善一下代码 , 加一个退出处理 ,无论哪个进程进行quit ,都进行退出
在这里插入图片描述
刚才的模型实际上还可以改进. 刚才我们的两个程序必须是轮流输入, 然后先发送才能接收, 那么如果我们希望能够同时发送和接收要怎么办呢 ?
发送的时候我们从键盘输入, 进程会阻塞 ,接收的时候 如果没有消息进程也会阻塞.
所以进程要么阻塞在输入上, 要么阻塞在接收上, 没有办法同时去处理. 所以这时候我们就可以利用多进程或多线程就是两个程序都改成多进程或多线程, 一个进程负责发送,另外一个进程负责接收这样的话就会互相不影响, 任何时候 你想发送就能发送, 只要有消息来, 你就能够. 立刻接收到, 除此之外我们还可以这么改进呢 ?

我们添加一个服务器, 通过服务器实现一个cs架构 ,那么每个客户端都往服务器发消息, 由服务器进行广播, 服务器进行转发,这样的话, 我一个客户端就可以同时向多个客户端发送消息,通过服务器来实现非常方便.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值