您当前的位置: 首页 >  操作系统

蔗理苦

暂无认证

  • 4浏览

    0关注

    88博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

2021-10-25 操作系统实验2——消息队列实验

蔗理苦 发布时间:2021-11-02 14:29:58 ,浏览量:4

文章目录
    • 一、实验目的
    • 二、实验任务
    • 三、实验要求
    • 四、实验过程
    • 五、思考

一、实验目的

了解和熟悉linux支持的消息通信机制。

二、实验任务

使用linux系统提供的系统调用msgget(),msgrev(),msgctl()编制一个长度为1K的消息发送和接受的程序。

三、实验要求
  1. 用一个程序作为“引子”,先后fork()两个进程,SERVER和CLIENT,进行通信。
  2. 由SERVER端创建一个Key为75的消息队列,等待CLIENT端进程发来的消息。当遇到类型为1的消息,则作为结束信号,删除该队列,并退出SERVER。SERVER每接受到一个消息后显示一句“(Server)received from + CLIENT端pid+接受消息的类型”,然后发送一个返回消息给CLIENT端,显示一句“(Server)sent”。
  3. CLIENT端使用key为75的消息队列,先后发送类型从10到1的消息,然后退出。发出的最后一个消息是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(Client)sent+发送消息的类型”,然后等待接受SERVER端的返回消息后,显示一句“(Client) received from + SERVER端pid+接受消息的类型”,再在发送下一条消息。
  4. 父进程在SERVER和CLIENT都退出后结束。
四、实验过程
  1. 用一个程序作为“引子”,先后fork()两个进程,SERVER和CLIENT,进行通信。

    int main() {
       pid_t pid1, pid2;
       while ((pid1 = fork()) == -1);
       if (pid1 == 0) {                         // 子进程 1
          SERVER();
       }
       else {
          while ((pid2 = fork()) == -1);
          if (pid2 == 0)                       // 子进程 2
             CLIENT();
          else wait(0);
       }
    }
    

    程序创建了两个子进程,一个是用 pid1 标识,另一个用 pid2 标识。根据程序代码可知,pid1 负责 SERVER() 函数,pid2 负责 CLIENT() 函数。最后父进程执行 wait(0) 函数,使得子进程存在时,父进程保持睡眠。

  2. 由SERVER端创建一个Key为75的消息队列,等待CLIENT端进程发来的消息。当遇到类型为1的消息,则作为结束信号,删除该队列,并退出SERVER。SERVER每接受到一个消息后显示一句“(Server)received from + CLIENT端pid+接受消息的类型”,然后发送一个返回消息给CLIENT端,显示一句“(Server)sent”。

    #define MSGKEY 75              // 消息队列的 key 为 75
    
    struct msgform {               // 消息结构
       long mtype;                // 消息类型
       char mtext[1024];          // 消息的文本长度为 1k
    } msg;
    
    int msgqid, i = 10;
    void CLIENT() {
       msgqid = msgget(MSGKEY, 0777);           // 打开消息队列
       while (i >= 1) {
          msg.mtype = i;                       // 消息的类型从 10 到 1
          // 对消息内容进行赋值
          sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype);
          msgsnd(msgqid, &msg, 1024, 0);       // 发送消息 msg 到 msgid 消息队列
          printf("(client) sent %ld\n", msg.mtype);
          i = i - 1;
          sleep(1);                            // 暂停 1 s,等待 SERVER 接收
    
          msgrcv(msgqid, &msg, 1024, 0, 0);    // 接收消息
          printf(msg.mtext);                   // 打印消息内容
       }
       exit(0);                          // 退出
    }
    

    在程序之初,我们定义了消息结构 msgform,并且使用 msgqid 来标识消息队列。 在 CLIENT() 函数中,首先使用 msgget() 函数打开由 SERVER 创建的消息队列,然后使用 while 循环尝试 10 次消息发送。发送完成后使用 sleep() 函数进行 1s 的时间间隔,使得 SERVER 能够接收到并做出反馈。最后进行来自 SERVER 消息的接收。

  3. CLIENT端使用key为75的消息队列,先后发送类型从10到1的消息,然后退出。发出的最后一个消息是SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(Client)sent+发送消息的类型”,然后等待接受SERVER端的返回消息后,显示一句“(Client) received from + SERVER端pid+接受消息的类型”,再在发送下一条消息。

    void SERVER() {
      msgqid = msgget(MSGKEY, 0777 |
                              IPC_CREAT);      // 创建一个所有用户都可以读、写、执行的队列
      do {
         msgrcv(msgqid, &msg, 1024, 0, 0);    // 从队列 msgid 接受消息 msg
         printf(msg.mtext);                   // 打印消息内容
         // 对消息内容进行赋值
         sprintf(msg.mtext, "(Client) received from SERVER %d, type %d\n", getpid(), msg.mtype);
         msgsnd(msgqid, &msg, 1024, 0);       // 接收消息
         printf("(Server) sent\n");
         sleep(1);                            // 暂停 1 s,等待 CLIENT 接收
      } while (msg.mtype != 1);                // 消息类型为 1 时,释放队列
      msgctl(msgqid, IPC_RMID, 0);             // 消除消息队列的标识符
      exit(0);                          // 退出
    }
    

    在 SERVER() 函数中,首先创建一个消息队列,名为 MSGKEY(75)。之后进入 do-while 循环,一直接收消息,直至消息类型为 1,退出循环并清空消息队列,函数结束。

  4. 父进程在SERVER和CLIENT都退出后结束。

    源程序:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MSGKEY 75              // 消息队列的 key 为 75
    
    struct msgform {               // 消息结构
       long mtype;                // 消息类型
       char mtext[1024];          // 消息的文本长度为 1k
    } msg;
    
    int msgqid, i = 10;
    
    void CLIENT() {
       msgqid = msgget(MSGKEY, 0777);           // 打开消息队列
       while (i >= 1) {
          msg.mtype = i;                       // 消息的类型从 10 到 1
          // 对消息内容进行赋值
          sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype);
          msgsnd(msgqid, &msg, 1024, 0);       // 发送消息 msg 到 msgid 消息队列
          printf("(client) sent %ld\n", msg.mtype);
          i = i - 1;
          sleep(1);                            // 暂停 1 s,等待 SERVER 接收
    
          msgrcv(msgqid, &msg, 1024, 0, 0);    // 接收消息
          printf(msg.mtext);                   // 打印消息内容
       }
       exit(0);                          // 退出
    }
    
    void SERVER() {
       msgqid = msgget(MSGKEY, 0777 |
                               IPC_CREAT);      // 创建一个所有用户都可以读、写、执行的队列
       do {
          msgrcv(msgqid, &msg, 1024, 0, 0);    // 从队列 msgid 接受消息 msg
          printf(msg.mtext);                   // 打印消息内容
          // 对消息内容进行赋值
          sprintf(msg.mtext, "(Client) received from SERVER %d, type %d\n", getpid(), msg.mtype);
          msgsnd(msgqid, &msg, 1024, 0);       // 接收消息
          printf("(Server) sent\n");
          sleep(1);                            // 暂停 1 s,等待 CLIENT 接收
       } while (msg.mtype != 1);                // 消息类型为 1 时,释放队列
       msgctl(msgqid, IPC_RMID, 0);             // 消除消息队列的标识符
       exit(0);                          // 退出
    }
    
    int main() {
       pid_t pid1, pid2;
       while ((pid1 = fork()) == -1);
       if (pid1 == 0) {                         // 子进程 1
          SERVER();
       }
       else {
          while ((pid2 = fork()) == -1);
          if (pid2 == 0)                       // 子进程 2
             CLIENT();
          else wait(0);
       }
    }
    

    实验截图:

    可以看到,在由 Client 发送消息后,Server 接收到消息并打印出“(Server)received from + CLIENT端pid+接受消息的类型”,然后发送一个返回消息“(Server) sent”给 Client,由 Client 接收并显示“(Client) received from + SERVER端pid+接受消息的类型”。由此循环10次,程序运行结束。

五、思考

(1)最初在实现 CLIENT 与 SERVER 的消息通信时,采用简单的“发送-接收”的方式,会发现两个进程发生异步现象:

void CLIENT() {
   msgqid = msgget(MSGKEY, 0777);           // 打开消息队列
   while (i >= 1) {
      msg.mtype = i;                       // 消息的类型从 10 到 1
      // 对消息内容进行赋值
      sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype);
      msgsnd(msgqid, &msg, 1024, 0);       // 发送消息 msg 到 msgid 消息队列
      printf("(client) sent %ld\n", msg.mtype);
      i = i - 1;
   }
   exit(0);                          // 退出
}

void SERVER() {
   msgqid = msgget(MSGKEY, 0777 |
                           IPC_CREAT);      // 创建一个所有用户都可以读、写、执行的队列
   do {
      msgrcv(msgqid, &msg, 1024, 0, 0);    // 从队列 msgid 接受消息 msg
      printf(msg.mtext);                   // 打印消息内容
   } while (msg.mtype != 1);                // 消息类型为 1 时,释放队列
   msgctl(msgqid, IPC_RMID, 0);             // 消除消息队列的标识符
   exit(0);                          // 退出
}

因为没有限制 CLIENT 和 SERVER 打印消息的顺序。

(2)后来尝试使用 P/V 操作实现前趋关系,为了操作方便,采用整型信号量:

typedef int semaphore;
semaphore S = 0;

void P(semaphore *S) {
   while (*S = 1) {
      msg.mtype = i;                       // 消息的类型从 10 到 1
      // 对消息内容进行赋值
      sprintf(msg.mtext, "(Server) received from CLIENT %d, type %d\n", getpid(), msg.mtype);
      msgsnd(msgqid, &msg, 1024, 0);       // 发送消息 msg 到 msgid 消息队列
      printf("(client) sent %ld\n", msg.mtype);
      i = i - 1;
      V(&S);
   }
   exit(0);                          // 退出
}

void SERVER() {
   msgqid = msgget(MSGKEY, 0777 |
                           IPC_CREAT);      // 创建一个所有用户都可以读、写、执行的队列
   do {
      P(&S);
      msgrcv(msgqid, &msg, 1024, 0, 0);    // 从队列 msgid 接受消息 msg
      printf(msg.mtext);                   // 打印消息内容
   } while (msg.mtype != 1);                // 消息类型为 1 时,释放队列
   msgctl(msgqid, IPC_RMID, 0);             // 消除消息队列的标识符
   exit(0);                          // 退出
}

发现仅输出了 CLIENT 的打印消息,推测原因可能是 P/V 操作放在循环里,CLIENT 总是先到达而一直运行,使得 SERVER 没有机会执行,父进程便结束了。 PS:在第四次实验中,采用了信号量集实验能够获得成功。仔细想想,应该还是该程序的信号量并没有进行共享,导致失败。

(3)最后多次尝试,发现使用 sleep() 函数能够达到较好的效果。整个过程中,思维得到了训练和提升。

关注
打赏
1657823434
查看更多评论
立即登录/注册

微信扫码登录

0.0497s