进程控制与通信,进程间通讯
我们可以使用各种不同组的三个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中可用。它们是信号量、共享内存和消息队列。我们知道它们提供的高级功能以及如何提供这些功能。一旦我们理解了这些函数,它们将为需要进程间通信的程序提供强大的解决方案。