进程控制与通信,进程间通讯

  进程控制与通信,进程间通讯

  我们可以使用各种不同组的三个IPC工具,但是因为我们需要传输的信息非常少,所以直接使用消息队列来实现请求的传输是显而易见的选择。

  如果需要传输大量数据,可以考虑使用共享内存来传输实际数据,使用信号量或消息来传输一个标记,通知其他进程共享内存中有数据可用。

  消息队列接口解决了我们在第11章中遇到的问题,即在传输数据时,我们需要两个进程来打开管道。使用消息队列允许进程将消息放入队列,即使该进程是当前队列的唯一用户。

  我们需要考虑的一个重要决策是将答案返回给客户。一个简单的选择是为服务器建立一个队列,为每个客户端建立一个队列。如果有大量的并发客户,大量的消息队列将会产生问题。通过使用消息中的消息ID字段,我们可以让所有用户使用一个队列,并通过使用消息中的客户端进程ID将响应发送到指定的客户端进程。因此,每个客户可以接收自己的消息,而将其他客户的消息留在队列中。

  要将我们的CD程序转换为使用IPC工具,我们只需要替换pipe_imp.c文件。在下一节中,我们将描述替换文件ipc_imp.c的主要部分

  实验-修改服务器功能

  1.首先,我们包含正确的头文件,声明消息队列键值,并定义存储消息数据的结构:

  #include "cd_data.h "

  #包含" cliserv.h "

  #包含sys/types.h

  #包含sys/ipc.h

  #包含sys/msg.h

  #定义服务器队列1234

  #定义客户端队列4321

  struct msg_passed {

  长整型msg _ key/*用于客户端pid */

  message_db_t实数_ message

  };

  两个全局变量保存由msgget函数返回的两个队列标识符:

  static int serv _ qid=-1;

  static int CLI _ qid=-1;

  3我们让服务器负责创建两个消息队列:

  int server_starting()

  {

  #if调试跟踪

  printf(" % d:-server _ starting()/n ",getpid());

  #endif

  serv _ qid=msg get((key _ t)SERVER _ MQUEUE,0666 IPC _ CREAT);

  if (serv_qid==-1)返回(0);

  CLI _ qid=msg get((key _ t)CLIENT _ MQUEUE,0666 IPC _ CREAT);

  if (cli_qid==-1)返回(0);

  返回(1);

  }

  4退出时服务器也负责清理。当服务器结束时,我们将全局变量设置为非法值。这样可以捕捉到服务器在调用server_ending后试图发送消息的bug。

  void server_ending()

  {

  #if调试跟踪

  printf("%d :- server_ending()/n ",getpid());

  #endif

  (void)msgctl(serv_qid,IPC_RMID,0);

  (void)msgctl(cli_qid,IPC_RMID,0);

  serv _ qid=-1;

  CLI _ qid=-1;

  }

  服务器读取函数从队列中读取任意消息,并返回消息的数据部分。

  int read_request_from_client(消息_数据库_ t *记录_指针)

  {

  struct msg _ passed my _ msg

  #if调试跟踪

  printf(" % d:-read _ request _ from _ client()/n ",getpid());

  #endif

  if (msgrcv(serv_qid,(void *) my_msg,sizeof(*rec_ptr),0,0)==-1) {

  return(0);

  }

  * rec _ ptr=my _ msg.real _ message

  返回(1);

  }

  6使用存储在请求中的标识消息的客户端进程ID来发送响应:

  int send _ resp _ to _ client(const message _ db _ t mess _ to _ send)

  {

  struct msg _ passed my _ msg

  #if调试跟踪

  printf(" % d:-send _ resp _ to _ client()/n ",getpid());

  #endif

  my _ msg . real _ message=mess _ to _ send;

  my _ msg . msg _ key=mess _ to _ send . client _ PID;

  if (msgsnd(cli_qid,(void *) my_msg,sizeof(mess_to_send),0)==-1) {

  return(0);

  }

  返回(1);

  }

  实验-修改客户端功能

  1当客户机启动时,他需要发现服务器和客户机队列标识符。客户端不创建队列。如果服务器没有运行,此功能将失败,因为消息队列不存在。

  int client_starting()

  {

  #if调试跟踪

  printf("%d :- client_starting/n ",getpid());

  #endif

  serv _ qid=msg get((key _ t)SERVER _ MQUEUE,0666);

  if (serv_qid==-1)返回(0);

  CLI _ qid=msg get((key _ t)CLIENT _ MQUEUE,0666);

  if (cli_qid==-1)返回(0);

  返回(1);

  }

  2和服务器一样,当客户端结束时,我们将全局变量设置为非法值。这将在客户端调用client_ending后试图发送消息时捕获bug。

  void client_ending()

  {

  #if调试跟踪

  printf("%d :- client_ending()/n ",getpid());

  #endif

  serv _ qid=-1;

  CLI _ qid=-1;

  }

  3为了向服务器发送消息,我们将数据存储在我们的结构中。注意,我们必须设置消息键值。因为0作为键值是非法的,不定义键就意味着他可以使用一个随机值,所以如果这个值恰好是0,函数就会失败。

  int send_mess_to_server

  {

  struct msg _ passed my _ msg

  #if调试跟踪

  printf(" % d:-send _ mess _ to _ server()/n ",getpid());

  #endif

  my _ msg . real _ message=mess _ to _ send;

  my _ msg . msg _ key=mess _ to _ send . client _ PID;

  if (msgsnd(serv_qid,(void *) my_msg,sizeof(mess_to_send),0)==-1) {

  perror("消息发送失败");

  return(0);

  }

  返回(1);

  }

  4当客户端服务器接收到一条消息时,它会使用自己的进程ID只接收发送给它的消息,而忽略其他进程的消息。

  int read _ resp _ from _ server(message _ db _ t * rec _ ptr)

  {

  struct msg _ passed my _ msg

  #if调试跟踪

  printf(" % d:-read _ resp _ from _ server()/n ",getpid());

  #endif

  if (msgrcv(cli_qid,(void *) my_msg,sizeof(*rec_ptr),getpid(),0)==-1) {

  return(0);

  }

  * rec _ ptr=my _ msg.real _ message

  返回(1);

  }

  5为了与pipe_imp.c完全兼容,我们需要定义另外四个函数。然而,在我们的新程序中,这个函数是空的。使用管道时,不再需要它们实现的操作。

  int start_resp_to_client(常量消息_db_t mess_to_send)

  {

  返回(1);

  }

  void end_resp_to_client(无效)

  {

  }

  int start_resp_from_server(空)

  {

  返回(1);

  }

  void end_resp_from_server

  {

  }

  这个程序到消息队列的转换展示了IPC消息队列的威力。我们需要的功能更少,需要的东西也比之前的实现少很多。

  IPC状态函数

  虽然X/Open不是必需的,但是大多数Linux都提供了一组命令,允许命令行访问IPC信息和清理不相关的IPC工具。这些是ipcs和ipcrm命令,它们在我们开发程序时非常有用。

  一个写的很差的程序或者一个因为某种原因失败的程序会离开它的IPC资源。这将使新程序调用失败,因为程序期望以一个干净的系统启动,但发现了一些遗留资源。Status (ipcs)和Clear (ipcrm)命令提供了一种检测和清除IPC遗留资源的方法。

  旗语

  要检测系统中信息的状态,可以使用ipcs -s命令。如果有一些信号量,输出将具有以下形式:

  $ ./ipcs -s

  ———信号量数组————

  semid所有者许可nsems状态

  768里克666 1

  我们可以使用ipcrm命令删除程序意外留下的信号量。要删除前面的信号量,可以使用以下命令:

  $ ./ipcrm -s 768

  一些较旧的Linux系统使用一些稍微不同的语法:

  $ ./ipcrm sem 768

  但是这种风格已经不推荐了。检查我们系统的手册页以确定我们系统上的格式。

  共用存储器

  与信号量类似,许多系统都提供了用于访问共享内存的命令行程序的详细信息。命令ipcs -m和ipcrm -m id。

  如以下输出示例所示:

  $ ipcs -m

  ———共享内存段————

  shmid所有者允许字节获取状态

  384里克666 4096 2

  这表明4KB共享内存段与两个进程相关联。

  Ipcrm -m id命令可以删除共享内存。当程序无法清理共享内存时,这很有用。

  用于消息队列的命令是ipcs -q和ipcrm -q id。

  如以下输出示例所示:

  $ ipcs -q

  ———消息队列————

  msqid所有者允许已用字节的消息

  384里克666 2048 2

  这表明消息队列中有两条消息,总共2048字节。

  Ipcrm -q id命令可以删除消息队列。

  在本章中,我们将了解三个进程间交互工具,它们首次在UNIX Systme V.2中广泛使用,并在Linux中可用。它们是信号量、共享内存和消息队列。我们知道它们提供的高级功能以及如何提供这些功能。一旦我们理解了这些函数,它们将为需要进程间通信的程序提供强大的解决方案。

进程控制与通信,进程间通讯