消息队列
消息队列 (也叫做报文队列)是Unix系统V版本中3种进程间通信机制之一。另外两种是信号量和共享内存。
这些IPC机制使用共同的授权方法。只有通过系统调用将标志符传递给核心之后,进程才能存取这些资源。这种系统IPC对象使用的控制方法和文件系统非常类似。使用对象的引用标志符作为资源表中的索引。
Linux采用消息队列的方式来实现消息传递。这种消息的发送方式是
发送方不必等待接收方检查它所收到的消息就可以继续工作下去,
而接收方如果没有收到消息也不需等待。
这种通信机制相对简单,但是应用程序使用起来就需要使用相对复杂的方式来应付了。新的消息总是放在队列的末尾,接收的时候并不总是从头来接收,可以从中间来接收。
IPC
进程间通信通过IPC对象,每个IPC对象都有唯一的ID号,通信双方需要获取该ID,创建者通过创建函数可以得到该值,可是另外的进程不能随意访问创建者的空间,于是约定利用相同的KEY值对于相同的ID,
IPC标识符
每一个I P C目标都有一个唯一的I P C标识符。这里所指的I P C目标是指一个单独的消息队列、一个信号量集或者一个共享的内存段。系统内核使用此标识符在系统内核中指明 I P C目标。
IPC 关键字
想要获得唯一的标识符,则必须使用一个 I P C关键字。客户端进程和服务器端进程必须双方都同意此关键字。这是建立一个客户机/服务器框架的第一步。在System V IPC机制中,建立两端联系的路由方法是和I P C关键字直接相关的。通过在应用程序中设置关键字值,每一次使用的关键字都可以是相同的。一般情况下,可以使用f t o k ( )函数为客户端和服务器端产生关键字值。
系统为每一个IPC对象保存一个ipc_perm结构体,该结构说明了IPC对象的权限和所有者,每一个版本的内核各有不用的ipc_perm结构成员。若要查看详细的定义请参阅文件
消息
通过消息队列将数据块从一个进程向另一个进程发送一个数据块。
消息实质上就是一些字或字节的序列(未必以空字符结尾)。它通过消息队列的方法在进程间传递。。
每个数据块或者序列都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
消息队列
消息队列是内核地址空间中的内部链表,通过linux内核在各个进程之间传递内容,消息顺序地发送到消息队列中,并且以几种不同的方式从队列中获取,每个消息队列可以用IPC标识符唯一的进行标识,内核中的消息队列是通过IPC的标识符来区别的,不同的消息队列之间是相互独立的,每个消息队列中的消息又构成一个独立的链表.
消息队列是一个消息的链表。就是把消息看作一个记录,并且这个记录具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读出消息。
消息队列是随内核持续的并和进程相关,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。因此系统中记录消息队列的数据结构 (struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中中找到访问入口。
消息队列的数据结构
用户空间数据结构
ipc_perm内核数据结构
系统为每一个IPC对象保存一个ipc_perm结构体,该结构说明了IPC对象的权限和所有者,每一个版本的内核各有不用的ipc_perm结构成员。
系统使用ipc_perm 结构来保存每个IPC 对象权限信息
它定义在头文件linux/ipc.h中
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_t mode;
unsigned short seq;
};
结构里的前几个成员的含义是明显的,分别是IPC 对象的关键字,uid 和gid。
然后是IPC 对象的创建者的 uid 和gid。
接下来的是IPC 对象的存取权限。最后一个成员也许有点难于理解,不过不要担心,这是系统保存的IPC 对象的使用频率信息,我们完全可以不去理会它。
可以使用cat /usr/include/linux/ipc.h
结构体ipc_perm保存着消息队列的一些重要的信息,比如说消息队列关联的键值,消息队列的用户id组id等。
struct msqid_ds *msgque[MSGMNI]向量:
msgque[MSGMNI]是一个msqid_ds结构的指针数组,每个msqid_ds结构指针代表一个系统消息队列,msgque[MSGMNI]的大小为MSGMNI的值决定了系统中消息队列的数目
可以使用cat /proc/sys/kernel/msgmni来查看
struct ipc_ids msg_ids是内核中记录消息队列的全局数据结构,其具体结构如下
struct msqid_ds
Linux内核中,每个消息队列都维护一个结构体,此结构体保存着消息队列当前状态信息。
msgqid_ds 结构被系统内核用来保存消息队列对象有关数据。内核中存在的每个消息队列对象系统都保存一个msgqid_ds 结构的数据存放该对象的各种信息。
在Linux 的库文件linux/msg.h 中,它的定义是这样的:
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*/
};
msg_perm 成员保存了消息队列的存取权限以及其他一些信息(见上面关于ipc_perm结构的介绍)。
msg_first 成员指针保存了消息队列(链表)中第一个成员的地址。
msg_last 成员指针保存了消息队列中最后一个成员的地址。
msg_stime 成员保存了最近一次队列接受消息的时间。
msg_rtime 成员保存了最近一次从队列中取出消息的时间。
msg_ctime 成员保存了最近一次队列发生改动的时间。
wwait 和rwait 是指向系统内部等待队列的指针。
msg_cbytes 成员保存着队列总共占用内存的字节数。
msg_qnum 成员保存着队列里保存的消息数目。
msg_qbytes 成员保存着队列所占用内存的最大字节数。
msg_lspid 成员保存着最近一次向队列发送消息的进程的pid。
msg_lrpid 成员保存着最近一次从队列中取出消息的进程的pid。
可以使用cat /usr/include/linux/msg.h
struct msg 消息节点结构:
msqid_ds.msg_first,msg_last维护的链表队列中的一个链表节点
消息队列在系统内核中是以消息链表的形式出现的。而完成消息链表每个节点结构定义的就是msg 结构
struct msg
{
msg *msg_next; /*下一个msg*/
long msg_type; /*消息类型*/
*msg_spot; /*消息体开始位置指针*/
msg_ts; /*消息体长度*/
message; /*消息体*/
}
msg_next 成员是指向消息链表中下一个节点的指针,依靠它对整个消息链表进行访问。
msg_type 和msgbuf 中mtype 成员的意义是一样的。
msg_spot 成员指针指出了消息内容(就是msgbuf 结构中的mtext)在内存中的位置。
msg_ts 成员指出了消息内容的长度。
msgbuf消息内容结构:
msg 消息节点中的消息体,也是消息队列使用进程(消息队列发送接收进程)发送或者接收的消息
消息队列最大的灵活性在于,我们可以自己定义传递给队列的消息的数据类型的。
不过这个类型并不是随便定义的,msgbuf 结构给了我们一个这类数据类型的基本结构定义。
在Linux 的系统库linux/msg.h 中,它是这样定义的:
struct msgbuf
{
long mtype; --消息类型
char mtext[n]; --消息内容
}
同样定义在linux/msg.h中
它有两个成员:
mtype 是一个正的长整型量,通过它来区分不同的消息数据类型。
mtext 是消息数据的内容。
通过设定mtype 值,我们可以进行单个消息队列的多向通讯。如下图,client 可以给它向server 发送的信息赋于一个特定的mtype 值,而server 向client 的信息则用另一个mtype值来标志。
这样,通过mtype 值就可以区分这两向不同的数据。
利用相同的原理,可以实现更复杂的例子。
需要注意的是,虽然消息的内容mtext 在msgbuf 中只是一个字符数组,但事实上,在我们定义的结构中,和它对应的部分可以是任意的数据类型,甚至是多个数据类型的集合。
比如我们可以定义这样的一个消息类型:
struct my_msgbuf
{
long mtype; /* Message type */
long request_id; /* Request identifier */
struct client info; /* Client information structure */
};
在这里,与mtext 对应的是两个数据类型,其中一个还是struct 类型。由此可见消息队列在传送消息上的灵活性。
不过,虽然没有类型上的限制,但Linux 系统还是对消息类型的最大长度做出了限制。
在Linux 的库文件linux/msg.h 中定义了每个msgbuf 结构的最大长度:
#define MSGMAX8192 /* <= INT_MAX */ /* max size of message (bytes) */
也即是说,包括mtype 所占用的4 个字节,每个msgbuf 结构最多只能只能占用4056字节的空间
内核空间数据结构
在比较新的3.x到4.x的内核源代码中,
原来的消息队列结构的定义被安装到了/includeuapi/linux/msg.h
而内核空间结构定义在源码include/linux/msg.h
msg_msg
msg_msg,消息的基本数据结构
每个msg_msg将占据一个page的内容,其中一个page除了存储该结构体,空余的部分直接用来存储消息的内容。其中记录了一条消息的type和size,next指针用于指向msg_msgseg结构。
在消息队列的设计中,若消息内容大于一个page,则会使用一个类似链表的结构,但是后面的节点不需要再标记type和size等数据,后面的节点用msg_msgseg表示。
/* one msg_msg structure for each message */
struct msg_msg
{
struct list_head m_list;
long m_type;
int m_ts; /* message text size */
struct msg_msgseg* next;
void *security; /* the actual message follows immediately */
};
msg_msgseg
//msg_msgseg只需要存储指向下一链表块的指针就行了,每个msg_msgseg也将占据一个page的空间,其中一个page除了存储该结构体,剩下的部分都将用来存储message的数据。
struct msg_msgseg
{
struct msg_msgseg* next; /* the next part of the message follows immediately */
};
//msg_msg链表的结构,每个page下方空余的部分都将用来存massage的text。
struct msg_queue
消息队列的数据结构,其中包含message、receiver、sender队列的链表指针,同时还包含其他一些相关数据。
/* one msq_queue structure for each present queue on the system */
struct msg_queue
{
struct kern_ipc_perm q_perm;
time_t q_stime; /* last msgsnd time */
time_t q_rtime; /* last msgrcv time */
time_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */
pid_t q_lspid; /* pid of last msgsnd */
pid_t q_lrpid; /* last receive pid */
struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
};
函数接口
内核中实现消息传递机制的代码基本上都在文件ipc/msg.c中,消息队列的主要调用有下面4个,这里只作简单介绍:
msgget:调用者提供一个消息队列的键标 (用于表示个消息队列的唯一名字),当这个消息队列存在的时候, 这个消息调用负责返回这个队列的标识号;如果这个队列不存在,就创建一个消息队列,然后返回这个消息队列的标识号 ,主要由sys_msgget执行。
msgsnd:向一个消息队列发送一个消息,主要由sys_msgsnd执行。
msgrcv:从一个消息队列中收到一个消息,主要由sys_msgrcv执行。
msgctl:在消息队列上执行指定的操作。根据参数的不同和权限的不同,可以执行检索、删除等的操作,主要由sys_msgctl执行。
ftok文件名到键值
key_t ftok (char*pathname, char proj);
它返回与路径pathname相对应的一个键值。该函数不直接对消息队列操作,但在调用ipc(MSGGET,…)或msgget()来获得消息队列描述字前,往往要调用该函数。典型的调用代码是:
key=ftok(path_ptr, 'a');
ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);
IPC同意用户接口
linux为操作系统V进程间通信的三种方式(消息队列、信号灯、共享内存区)提供了一个统一的用户界面:
int ipc(unsigned int call, int first, int second, int third, void * ptr, long fifth);
第一个参数指明对IPC对象的操作方式,对消息队列而言共有四种操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分别代表向消息队列发送消息、从消息队列读取消息、打开或创建消息队列、控制消息队列;first参数代表唯一的IPC对象;
下面将介绍四种操作。
int ipc( MSGGET, intfirst, intsecond, intthird, void*ptr, longfifth);
与该操作对应的系统V调用为:int msgget( (key_t)first,second)。
int ipc( MSGCTL, intfirst, intsecond, intthird, void*ptr, longfifth)
与该操作对应的系统V调用为:int msgctl( first,second, (struct msqid_ds*) ptr)。
int ipc( MSGSND, intfirst, intsecond, intthird, void*ptr, longfifth);
与该操作对应的系统V调用为:int msgsnd( first, (struct msgbuf*)ptr, second, third)。
int ipc( MSGRCV, intfirst, intsecond, intthird, void*ptr, longfifth);
与该操作对应的系统V调用为:int msgrcv( first,(struct msgbuf*)ptr, second, fifth,third),
注:本人不主张采用系统调用ipc(),而更倾向于采用系统V或者POSIX进程间通信API。
原因如下:
虽然该系统调用提供了统一的用户界面,但正是由于这个特性,它的参数几乎不能给出特定的实际意义(如以first、second来命名参数),在一定程度上造成开发不便。
正如ipc手册所说的:ipc()是linux所特有的,编写程序时应注意程序的移植性问题;
该系统调用的实现不过是把系统V IPC函数进行了封装,没有任何效率上的优势;
系统V在IPC方面的API数量不多,形式也较简洁。
创建新消息队列或取得已存在消息队列
int msgget(key_t key, int msgflg);
参数
描述
key
可以认为是一个端口号,也可以由函数ftok生成
msgflg
标识,可以取IPC_CREAT或者IPC_EXCL
调用返回:成功返回消息队列描述字,否则返回-1。
参数key是一个键值,由ftok获得;
msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。
在以下两种情况下,该调用将创建一个新的消息队列:
* 如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
key参数为IPC_PRIVATE;
注:参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。\
参数msgflg可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。
IPC_CREAT值,若没有该队列,则创建一个并返回新标识符;若已存在,则返回原标识符。
IPC_EXCL值,若没有该队列,则返回-1;若已存在,则返回0。
向队列读/写消息
msgrcv从队列中取用消息:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgsnd将数据放到消息队列中:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数
描述
msqid
消息队列的标识码
msgp
指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,
msgsz
消息的大小
msgtyp
从消息队列内读取的消息形态。如果值为零,则表示消息队列中的所有消息都会被读取。否则只接收相同类型的消息
msgflg
用来指明核心程序在队列没有数据的情况下所应采取的行动。
其中msgp是用户通用的结构,可以使用如下形式
struct msgstru{
long mtype; //大于0
char mtext[512];
};
如果msgflg和常数IPC_NOWAIT合用,则在msgsnd()执行时若是消息队列已满,则msgsnd()将不会阻塞,而会立即返回-1,如果执行的是msgrcv(),则在消息队列呈空时,不做等待马上返回-1,并设定错误码为ENOMSG。当msgflg为0时,msgsnd()及msgrcv()在队列呈满或呈空的情形时,采取阻塞等待的处理模式。
读操作msgrcv
int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
调用返回:成功返回读出消息的实际字节数,否则返回-1。
该系统调用从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。
msqid为消息队列描述字;消息返回后存储在msgp指向的地址,
msgsz指定msgbuf的mtext成员的长度(即消息内容的长度),
msgtyp为请求读取的消息类型;
读消息标志msgflg可以为以下几个常值的或:
IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
IPC_EXCEPT 与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的
msgsz字节,则把该消息截断,截断部分将丢失。
msgrcv手册中详细给出了消息类型取不同值时(>0; <0; =0),调用将返回消息队列中的哪个消息。
msgrcv()解除阻塞的条件有三个:
消息队列中有了满足条件的消息;
msqid代表的消息队列被删除;
调用msgrcv()的进程被信号中断;
写操作msgsnd
int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
调用返回:成功返回0,否则返回-1。
向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgze指定。
对发送消息来说,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。
造成msgsnd()等待的条件有两种:
当前消息的大小与当前消息队列中的字节数之和超过了消息队列
的总容量;
当前消息队列的消息数(单位”个”)不小于消息队列的总容量(单位”字节数”),此时,虽然消息队列中的消息数目很多,但基本上都只有一个字节。
msgsnd()解除阻塞的条件有三个:
不满足上述两个条件,即消息队列中有容纳该消息的空间;
msqid代表的消息队列被删除;
调用msgsnd()的进程被信号中断;
设置消息队列属性
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
调用返回:成功返回0,否则返回-1。
IPC_STAT : 该命令用来获取消息队列对应的 msqid_ds 数据结构,返回的信息存贮在buf指向的msqid结构中;
IPC_SET : 该命令用来设置消息队列的属性,设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
IPC_RMID : 从内核中删除 msqid 标识的消息队列。
消息队列的限制
每个消息队列的容量(所能容纳的字节数)都有限制,该值因系统不同而不同。在后面的应用实例中,输出了redhat 8.0的限制,结果参见 附录 3。
另一个限制是每个消息队列所能容纳的最大消息数:在redhad 8.0中,该限制是受消息队列容量制约的:消息个数要小于消息队列的容量(字节数)。
注:上述两个限制是针对每个消息队列而言的,系统对消息队列的限制还有系统范围内的最大消息队列个数,以及整个系统范围内的最大消息数。一般来说,实际开发过程中不会超过这个限制。
IPCS和IPCRM命令
IPCS 命令
命令ipcs用于读取System V IPC目标的状态。
ipcs -q: 只显示消息队列。
ipcs -s: 只显示信号量。
ipcs -m: 只显示共享内存。
ipcs –help: 其他的参数。
IPCRM命令
命令IPCRM用来删除System V IPC目标的状态。
移除一个消息对象。或者共享内存段,或者一个信号集,同时会将与ipc对象相关链的数据也一起移除。当然,只有超级管理员,或者ipc对象的创建者才有这项权利
ipcrm用法
ipcrm -M shmkey 移除用shmkey创建的共享内存段
ipcrm -m shmid 移除用shmid标识的共享内存段
ipcrm -Q msgkey 移除用msqkey创建的消息队列
ipcrm -q msqid 移除用msqid标识的消息队列
ipcrm -S semkey 移除用semkey创建的信号
ipcrm -s semid 移除用semid标识的信号
示例
所以我们在这里将会编写两个程序,msg_receive和msg_send来表示接收和发送信息。
根据正常的情况,我们允许两个程序都可以创建消息,但只有接收者在接收完最后一个消息之后,它才把它删除。
发送端msg_send
#include
#include
#include
#include
#include
#include
#define MSG_KEY 1024
#define MAX_TEXT 512
struct msg_st
{
long int msg_type;
char text[MAX_TEXT];
};
int main()
{
int running = 1;
struct msg_st data;
char buffer[BUFSIZ];
int msgid = -1;
//建立消息队列
msgid = msgget(MSG_KEY, IPC_EXCL); /*检查消息队列是否存在*/
if(msgid < 0)
{
msgid = msgget((key_t)MSG_KEY, IPC_CREAT|0666);/*创建消息队列*/
if(msgid < 0)
{
printf("failed to create msq, errno=%d : %s\n",errno,strerror(errno));
exit(-1);
}
}
//向消息队列中写消息,直到写入end
while(running)
{
//输入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
data.msg_type = 1; //注意2
strcpy(data.text, buffer);
//向队列发送数据
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
//输入end结束输入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
return EXIT_SUCCESS;
}
接收端msg_receive
#include
#include
#include
#include
#include
#include
#define MSG_KEY 1024
struct msg_st
{
long int msg_type;
char text[BUFSIZ];
};
int main()
{
int running = 1;
int msgid = -1;
struct msg_st data;
long int msgtype = 0; //注意1
//建立消息队列
//建立消息队列
msgid=msgget(MSG_KEY, IPC_EXCL); /*检查消息队列是否存在*/
if(msgid < 0)
{
msgid = msgget(MSG_KEY, IPC_CREAT|0666);/*创建消息队列*/
if(msgid < 0)
{
printf("failed to create msq, errno=%d : %s\n",errno,strerror(errno));
exit(-1);
}
msgid = msgget((key_t)MSG_KEY, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
}
//从队列中获取消息,直到遇到end消息为止
while(running)
{
if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
{
fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
exit(EXIT_FAILURE);
}
printf("You wrote: %s\n", data.text);
//遇到end结束
if(strncmp(data.text, "end", 3) == 0)
{
running = 0;
}
}
//删除消息队列
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
运行结果
这里主要说明一下消息类型是怎么一回事
注意msg_receive.c文件main函数中定义的变量msgtype(注释为注意1),它作为msgrcv函数的接收信息类型参数的值,其值为0,表示获取队列中第一个可用的消息。
再来看看msg_send.c文件中while循环中的语句data.msg_type = 1(注释为注意2),它用来设置发送的信息的信息类型,即其发送的信息的类型为1。所以程序msg_receive能够接收到程序msg_send发送的信息。
msgtype如果值为零,则表示消息队列中的所有消息都会被读取。否则只接收相同类型的消息
如果把注意1,即msg_receive.c文件main函数中的语句由long int msgtype = 0;改变为long int msgtype = 2;会发生什么情况,msgreceive将不能接收到程序msgsend发送的信息。因为在调用msgrcv函数时,如果msgtype(第四个参数)大于零,则将只获取具有相同消息类型的第一个消息,修改后获取的消息类型为2,而msgsend发送的消息类型为1,所以不能被msgreceive程序接收。