1、MSG一、实验目的1、了解什么是消息2、熟悉消息传送的机理。二、实验内容消息的创建、发送和接收。使用系统调用 msgget( ),msgsnd( ),msgrev( ),及 msgctl( )编制一长度为k 的消息发送和接收的程序三、实验内容指导提示(一) 、什么是消息消息(message)是一个格式化的可变长的信息单元。消息机制允许由一个进程给其它任意的进程发送一个消息。当一个进程收到多个消息时,可将它们排成一个消息队列。消息使用二种重要的数据结构:一是消息首部,其中记录了一些与消息有关的信息,如消息数据的字节数;二个消息队列头表,其每一表项是作为一个消息队列的消息头,记录了消息队列的有关信
2、息。1、消息机制的数据结构(1)消息首部记录一些与消息有关的信息,如消息的类型、大小、指向消息数据区的指针、消息队列的链接指针等。(2)消息队列头表其每一项作为一个消息队列的消息头,记录了消息队列的有关信息如指向消息队列中第一个消息和指向最后一个消息的指针、队列中消息的数目、队列中消息数据的总字节数、队列所允许消息数据的最大字节总数,还有最近一次执行发送操作的进程标识符和时间、最近一次执行接收操作的进程标识符和时间等。2、消息队列的描述符UNIX 中,每一个消息队列都有一个称为关键字(key)的名字,是由用户指定的;消息队列有一消息队列描述符,其作用与用户文件描述符一样,也是为了方便用户和系统
3、对消息队列的访问。(二) 、涉及的系统调用1. msgget( )创建一个消息,获得一个消息的描述符。核心将搜索消息队列头表,确定是否有指定名字的消息队列。若无,核心将分配一新的消息队列头,并对它进行初始化,然后给用户返回一个消息队列描述符,否则它只是检查消息队列的许可权便返回。系统调用格式:msgqid=msgget(key,flag)该函数使用头文件如下:#include#include#include参数定义int msgget(key,flag)key_t key;int flag;其中:key 是用户指定的消息队列的名字;flag 是用户设置的标志和访问方式。如 IPC_CREAT
4、|0400 是否该队列已被创建。无则创建,是则打开;IPC_EXCL |0400 是否该队列的创建应是互斥的。msgqid 是该系统调用返回的描述符,失败则返回-1。2. msgsnd()发送一消息。向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部。系统调用格式:msgsnd(msgqid,msgp,size,flag)该函数使用头文件如下:#include #include #include 参数定义:int msgsnd(msgqid,msgp,size,flag)I int msgqid,size,flag;struct msgbuf * msgp;其中 msgqid 是返
5、回消息队列的描述符;msgp 是指向用户消息缓冲区的一个结构体指针。缓冲区中包括消息类型和消息正文,即long mtype; /*消息类型*/char mtext ; /*消息的文本*/size 指示由 msgp 指向的数据结构中字符数组的长度;即消息的长度。这个数组的最大值由 MSG-MAX( )系统可调用参数来确定。flag 规定当核心用尽内部缓冲空间时应执行的动作: 进程是等待,还是立即返回。若在标志 flag 中未设置 IPC_NOWAIT 位,则当该消息队列中的字节数超过最大值时,或系统范围的消息数超过某一最大值时,调用 msgsnd 进程睡眠。若是设置 IPC_NOWAIT,则在此
6、情况下,msgsnd 立即返回。对于 msgsnd( ),核心须完成以下工作:(1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回;(2)核心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区;(3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据;(4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。3. msgrcv( )接受一消息。从指定的消息队列中接收指定类型的消息。系统调用格式:msgrcv(msgqid,msgp,size,type,flag)本函数
7、使用的头文件如下:#include #include #include 参数定义:int msgrcv(msgqid,msgp,size,type,flag)int msgqid,size,flag;struct msgbuf *msgp;long type;其中,msgqid,msgp,size,flag 与 msgsnd 中的对应参数相似,type 是规定要读的消息类型,flag 规定倘若该队列无消息,核心应做的操作。如此时设置了 IPC_NOWAIT 标志,则立即返回,若在 flag 中设置了 MS_NOERROR,且所接收的消息大于 size,则核心截断所接收的消息。对于 msgrcv
8、 系统调用,核心须完成下述工作:(1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回;(2)根据 type 的不同分成三种情况处理:type=0,接收该队列的第一个消息,并将它返回给调用者;type 为正整数,接收类型 type 的第一个消息;type 为负整数,接收小于等于 type 绝对值的最低类型的第一个消息。(3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。4. msgctl( )消息队列的操纵。读取消息队列的状态信息并进行修改,如查询消息队列描
9、述符、修改它的许可权及删除该队列等。系统调用格式:msgctl(msgqid,cmd,buf);本函数使用的头文件如下:#include #include #include 参数定义:int msgctl(msgqid,cmd,buf);int msgqid,cmd;struct msgqid_ds *buf;其中,函数调用成功时返回 0,不成功则返回-1。buf 是用户缓冲区地址,供用户存放控制参数和查询结果;cmd 是规定的命令。命令可分三类:(1)IPC_STAT。查询有关消息队列情况的命令。如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程标识符、发送时间等;(2)IP
10、C_SET。按 buf 指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等;(3)IPC_RMID。消除消息队列的标识符。msgqid_ds 结构定义如下:struct msgqid_ds struct ipc_perm msg_perm; /*许可权结构*/short pad17; /*由系统使用*/ushort msg_qnum; /*队列上消息数*/ushort msg_qbytes; /*队列上最大字节数*/ushort msg_lspid; /*最后发送消息的 PID*/ushort msg_lrpid; /*最后接收消息的 PID*/
11、time_t msg_stime; /*最后发送消息的时间 */time_t msg_rtime; /*最后接收消息的时间*/time_t msg_ctime; /*最后更改时间*/;struct ipc_perm ushort uid; /*当前用户*/ushort gid; /*当前进程组*/ushort cuid; /*创建用户*/ushort cgid; /*创建进程组*/ushort mode; /*存取许可权*/ short pid1; long pad2; /*由系统使用*/ (三) 、参考程序1、client.c#include #include #include #defin
12、e MSGKEY 75struct msgform long mtype;char mtext1000;msg;int msgqid;void client()int i;msgqid=msgget(MSGKEY,0777); /*打开 75#消息队列*/for(i=10;i=1;i-)msg.mtype=i;printf(“(client)sentn”);msgsnd(msgqid, /*发送消息*/exit(0);main( ) client( );2、server.c#include #include #include #define MSGKEY 75struct msgform lo
13、ng mtype;char mtext1000;msg;int msgqid;void server( )msgqid=msgget(MSGKEY,0777|IPC_CREAT); /*创建 75#消息队列*/do msgrcv(msgqid, /*接收消息*/printf(“(server)receivedn”);while(msg.mtype!=1);msgctl(msgqid,IPC_RMID,0); /*删除消息队列,归还资源*/exit(0);main( ) server( );(四) 、程序说明1、为了便于操作和观察结果,编制二个程序 client.c 和 server.c,分别用
14、于消息的发送与接收。2、server 建立一个 Key 为 75 的消息队列,等待其它进程发来的消息。当遇到类型为1 的消息,则作为结束信号,取消该队列,并退出 server。server 每接收到一个消息后显示一句“(server)received。 ”3、client 使用 key 为 75 的消息队列,先后发送类型从 10 到 1 的消息,然后退出。最后一个消息,即是 server 端需要的结束信号。client 每发送一条消息后显示一句 “(client)sent”。4、注意: 二个程序分别编辑、编译为 client 与 server。执行:./serverint *addr;void
15、 * shmat();/*如果去掉这行,看看运行结果,分析为什么*/CLIENT()int i;shmid=shmget(SHMKEY,1024, 0777|IPC_CREAT); /*获取共享区,长度 1024,关键词 SHMKEY*/addr=shmat(shmid,0,0); /*共享区起始地址为 addr*/for(i=9;i=0;i-) while(*addr!= -1); printf(“(client)sentn“); /*打印(client)sent*/*addr=i; /*把 i 赋给 addr*/exit(0);SERVER() dowhile(*addr = =-1);p
16、rintf(“(server)receivedn%d“,*addr); /*服务进程使用共享区*/if(*addr!=0)*addr=-1; while(*addr);wait(0);shmctl(shmid,IPC_RMID,0);main()shmid=shmget(SHMKEY,1024,0777|IPC_CREAT); /*创建共享区*/addr=shmat(shmid,0,0); /*共享区起始地址为 addr*/*addr=-1;if(fork()SERVER();elseCLIENT();结果运行的结果和预想的完全一样。但在运行的过程中,发现每当 client 发送一次数据后,s
17、erver 要等大约 0.1 秒才有响应。同样,之后 client 又需要等待大约 0.1 秒才发送下一个数据。分析出现上述的应答延迟的现象是程序设计的问题。当 client 端发送了数据后,并没有任何措施通知 server 端数据已经发出,需要由 client 的查询才能感知。此时,client 端并没有放弃系统的控制权,仍然占用 CPU 的时间片。只有当系统进行调度时,切换到了server 进程,再进行应答。这个问题,也同样存在于 server 端到 client 的应答过程之中。3 比较两种消息通信机制中的数据传输的时间由于两种机制实现的机理和用处都不一样,难以直接进行时间上的比较。如果
18、比较其性能,应更加全面的分析。(1)消息队列的建立比共享区的设立消耗的资源少.前者只是一个软件上设定的问题,后者需要对硬件操作,实现内存的映像,当然控制起来比前者复杂.如果每次都重新进行队列或共享的建立,共享区的设立没有什么优势。(2)当消息队列和共享区建立好后,共享区的数据传输,受到了系统硬件的支持,不耗费多余的资源;而消息传递,由软件进行控制和实现,需要消耗一定的 CPU 资源.从这个意义上讲,共享区更适合频繁和大量的数据传输.(3)消息的传递,自身就带有同步的控制.当等到消息的时候,进程进入睡眠状态,不再消耗 CPU 资源.而共享队列如果不借助其他机制进行同步,接受数据的一方必须进行不断的查询,白白浪费了大量的 CPU 资源.可见消息方式的使用更加灵活.】