实验五 消息的发送与接收

 实验五  消息的发送与接收

实验目的
    1、了解什么是消息
    2、熟悉消息传送的机理

实验内容
消息的创建、发送和接收。使用系统调用msgget( ),msgsnd( ),msgrev( ),及msgctl( )编制一长度为1k的消息发送和接收的程序。

实验指导
    一、什么是消息
消息(message)是一个格式化的可变长的信息单元。消息机制允许由一个进程给其它任意的进程发送一个消息。当一个进程收到多个消息时,可将它们排成一个消息队列。消息使用二种重要的数据结构:一是消息首部,其中记录了一些与消息有关的信息,如消息数据的字节数;二是消息队列头表,其每一表项是作为一个消息队列的消息头,记录了消息队列的有关信息。
1、消息机制的数据结构
(1)消息首部
记录一些与消息有关的信息,如消息的类型、大小、指向消息数据区的指针、消息队列的链接指针等。
(2)消息队列头表
其每一项作为一个消息队列的消息头,记录了消息队列的有关信息如指向消息队列中第一个消息和指向最后一个消息的指针、队列中消息的数目、队列中消息数据的总字节数、队列所允许消息数据的最大字节总数,还有最近一次执行发送操作的进程标识符和时间、最近一次执行接收操作的进程标识符和时间等。
2、消息队列的描述符
UNIX中,每一个消息队列都有一个称为关键字(key)的名字,是由用户指定的;消息队列有一消息队列描述符,其作用与用户文件描述符一样,也是为了方便用户和系统对消息队列的访问。
二、涉及的系统调用
1. msgget( )
创建一个消息,获得一个消息的描述符。核心将搜索消息队列头表,确定是否有指定名字的消息队列。若无,核心将分配一新的消息队列头,并对它进行初始化,然后给用户返回一个消息队列描述符,否则它只是检查消息队列的许可权便返回。
系统调用格式:
msgqid=msgget(key,flag)
该函数使用头文件如下:
#include
#include
#include
参数定义
int msgget(key,flag)
key_t  key;
int  flag;
其中:
key是用户指定的消息队列的名字;flag是用户设置的标志和访问方式。如  IPC_CREAT |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是返回消息队列的描述符;msgp是指向用户消息缓冲区的一个结构体指针。缓冲区中包括消息类型和消息正文,即
{
    long  mtype;             /*消息类型*/
    char  mtext[ ];           /*消息的文本*/
 }
size指示由msgp指向的数据结构中字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX( )系统可调用参数来确定。flag规定当核心用尽内部缓冲空间时应执行的动作:进程是等待,还是立即返回。若在标志flag中未设置IPC_NOWAIT位,则当该消息队列中的字节数超过最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。
对于msgsnd( ),核心须完成以下工作:
(1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回;
(2)核心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区;
(3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据;
(4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。
3. msgrcv( )
接受一消息。从指定的消息队列中接收指定类型的消息。
系统调用格式:
           msgrcv(msgqid,msgp,size,type,flag)
本函数使用的头文件如下:
     #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系统调用,核心须完成下述工作:
(1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回;
(2)根据type的不同分成三种情况处理:
type=0,接收该队列的第一个消息,并将它返回给调用者;
type为正整数,接收类型type的第一个消息;
type为负整数,接收小于等于type绝对值的最低类型的第一个消息。
(3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。
4. msgctl( )
消息队列的操纵。读取消息队列的状态信息并进行修改,如查询消息队列描述符、修改它的许可权及删除该队列等。
系统调用格式:
             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)IPC_SET。按buf指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等;
(3)IPC_RMID。消除消息队列的标识符。
msgqid_ds  结构定义如下:
struct  msgqid_ds
   {  struct  ipc_perm  msg_perm;     /*许可权结构*/
      short  pad1[7];                 /*由系统使用*/
      ushort msg_qnum;               /*队列上消息数*/
      ushort msg_qbytes;              /*队列上最大字节数*/
      ushort msg_lspid;               /*最后发送消息的PID*/
      ushort msg_lrpid;               /*最后接收消息的PID*/
      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
#define MSGKEY 75
struct  msgform
  {  long  mtype;
     char  mtext[1000];
}msg;
int  msgqid;

void client()
{
      int i;
msgqid=msgget(MSGKEY,0777);   /*打开75#消息队列*/
for(i=10;i>=1;i--)
{
  msg.mtype=i;
printf(“(client)sent/n”);
msgsnd(msgqid,&msg,1024,0);     /*发送消息*/
}
exit(0);
}

main( )
{
  client( );
}

2、server.c
#include
#include
#include
#define MSGKEY 75
struct  msgform
  {  long  mtype;
     char  mtext[1000];
}msg;
int  msgqid;

void server( )
{
        msgqid=msgget(MSGKEY,0777|IPC_CREAT);  /*创建75#消息队列*/
do
          {
msgrcv(msgqid,&msg,1030,0,0);   /*接收消息*/
    printf(“(server)received/n”);
}while(msg.mtype!=1);
msgctl(msgqid,IPC_RMID,0);  /*删除消息队列,归还资源*/
exit(0);
}

main( )

server( );
}
四、程序说明
1、为了便于操作和观察结果,编制二个程序client.c和server.c,分别用于消息的发送与接收。
2、server建立一个 Key 为75的消息队列,等待其它进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出server。server每接收到一个消息后显示一句“(server)received。”
3、client使用 key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后一个消息,即是 server端需要的结束信号。client 每发送一条消息后显示一句 “(client)sent”。
4、注意:   二个程序分别编辑、编译为client与server。执行:
./server&
ipcs  -q
./client。
五、运行结果
从理想的结果来说,应当是每当client发送一个消息后,server接收该消息,client再发送下一条。也就是说“(client)sent”和 “(server)received”的字样应该在屏幕上交替出现。实际的结果大多是,先由client发送了两条消息,然后server接收一条消息。此后client 、server交替发送和接收消息。最后server一次接收两条消息。client 和server 分别发送和接收了10条消息,与预期设想一致。
六、思考
message的传送和控制并不保证完全同步,当一个程序不在激活状态的时候,它完全可能继续睡眠,造成了上面的现象,在多次send message 后才recieve message。这一点有助于理解消息传送的实现机理。

你可能感兴趣的:(OS(Linux))