linux进程通讯之CD程序

CD数据库程序 

现在我们可以使用我们在这一章所了解的IPC工具来修改我们的CD数据库程序。


我们可以使用三种IPC工具的多种不同组,但是因为我们需要传送的信息很少,直接使用消息队列实现请求的传递是一个很明显的选择。


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


消息队列接口解决我们了在第11章所遇到的问题,即当数据传递时我们需要两个进程使得管道打开。使用消息队列可以使得一个进程将消息放入队列,尽管这个进程是当前队列的唯一用户。


我们需要考虑的一个重要决定就是将答案返回给客户。一个简单的选择就是使得一个队列用于服务器而且每个客户有一个队列。如果有大量的并发客户,由于需要大量的消息队列就会引起问题。通过使用消息中的消息ID域,我们可以使得所有的用户使用一个队列,并且通过在消息中使用客户端进程ID来将响应发送给指定的客户端进程。从而每一个客户可以接收只属于他自己的消息,而将其他客户的消息留在队列中。


要转换我们的CD程序来使用IPC工具,我们只需要替换pipe_imp.c文件。在下面的部分,我们将会描述替换文件ipc_imp.c的原则部分。


试验--修改服务器函数 

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

1
2
3
4
5
6
7
8
9
10
11
#include “cd_data.h”
#include “cliserv.h”
#include <sys/types.h>
#include <sys/ipc.h>
#include 
#define SERVER_MQUEUE 1234
#define CLIENT_MQUEUE 4321
struct msg_passed {
    long int msg_key; /* used for client pid */
    message_db_t real_message;
};

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

1
2
static int serv_qid = -1;
static int cli_qid = -1;

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

1
2
3
4
5
6
7
8
9
10
11
int server_starting()
{
    #if DEBUG_TRACE
        printf(“%d :- server_starting()/n”,  getpid());
    #endif
    serv_qid = msgget((key_t)SERVER_MQUEUE, 0666 | IPC_CREAT);
    if (serv_qid == -1) return(0);
    cli_qid = msgget((key_t)CLIENT_MQUEUE, 0666 | IPC_CREAT);
    if (cli_qid == -1) return(0);
    return(1);
}


4 服务器同时负责退出时的清理工作。当服务器结束时,我们设置我们的全局变量为非法值。这就会捕获服务器尝试在调用server_ending之后发送消息的bug。

1
2
3
4
5
6
7
8
9
10
void server_ending()
{
    #if DEBUG_TRACE
        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;
}

5 服务器read函数由队列中读取一条任意的消息,并且返回消息的数据部分。

1
2
3
4
5
6
7
8
9
10
11
12
int read_request_from_client(message_db_t *rec_ptr)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
        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;
    return(1);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
int send_resp_to_client(const message_db_t mess_to_send)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
        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);
    }
    return(1);
}


试验--修改客户端函数 

1 当客户端启动时,他需要发现服务器与客户端队列标识符。客户端并不创建队列。如果服务器没有运行,这个函数就会失败,因为消息队列并不存在。

1
2
3
4
5
6
7
8
9
10
11
int client_starting()
{
    #if DEBUG_TRACE
        printf(“%d :- client_starting/n”,  getpid());
    #endif
    serv_qid = msgget((key_t)SERVER_MQUEUE, 0666);
    if (serv_qid == -1) return(0);
  cli_qid = msgget((key_t)CLIENT_MQUEUE, 0666);
  if (cli_qid == -1) return(0);
  return(1);
}


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

1
2
3
4
5
6
7
8
void client_ending()
{
    #if DEBUG_TRACE
        printf(“%d :- client_ending()/n”, getpid());
    #endif
    serv_qid = -1;
    cli_qid = -1;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int send_mess_to_server(message_db_t mess_to_send)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
        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(“Message send failed”);
        return(0);
    }
    return(1);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
int read_resp_from_server(message_db_t *rec_ptr)
{
    struct msg_passed my_msg;
    #if DEBUG_TRACE
        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;
  return(1);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int start_resp_to_client(const message_db_t mess_to_send)
{
    return(1);
}
void end_resp_to_client(void)
{
}
int start_resp_from_server(void)
{
    return(1);
}
void end_resp_from_server(void)
{
}


此程序到消息队列的转换演示了IPC消息队列的强大。我们需要更少的函数,而我们所需要要比以前的实现少得多。

IPC状态函数 


尽管X/Open并没有要求,大多数的Linux提供了一个命令集合来允许命令行访问IPC信息,并且清理无关联的IPC工具。这就是ipcs与ipcrm命令,当我们开发程序时,这是非常有用的。


编写糟糕的程序或是因为某些原因失败的程序会遗留其IPC资源。这会使得新的程序调用失败,因为程序期望以一个干净的系统开始,但是却发现一些遗留的资源。状态(ipcs)与清除(ipcrm)命令提供了一个检测与清除IPC遗留资源的一种方法。


信号量

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

1
2
3
4
$ ./ipcs -s
——— Semaphore Arrays ————
semid     owner     perms nsems status
768       rick      666   1


我们可以使用ipcrm命令来移除由程序偶然留下的信号量。要删除前面的信号量,可以使用下面的命令:

1
$ ./ipcrm -s 768

一些较老的Linux系统使用一些略微不同的语法:

1
$ ./ipcrm sem 768

但是这种风格不再推荐使用。查看我们系统的手册页来确定在我们的系统上是哪种格式。

共享内存

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

如下面的例子输出:

1
2
3
4
$ ipcs -m
——— Shared Memory Segments ————
shmid     owner     perms     bytes nattch status
384       rick      666       4096  2


这显示一个4KB的共享内存段与两个进程相关联。

ipcrm -m 命令可以移除共享内存。当一个程序清理共享内存失败时,这会十分有用。


消息队列 

对于消息队列的命令为ipcs -q与ipcrm -q 。


如下面的例子输出:

1
2
3
4
$ ipcs -q
——— Message Queues ————
msqid     owner     perms used-bytes messages
384       rick      666   2048       2


这显示了在消息队列中有两个消息,共计2048字节。

ipcrm -q 命令可以移除消息队列。


总结 

在这一章,我们了解了首次在UNIX Systme V.2中广泛使用并且在Linux中可用的三种进程间交互工具。他们是信号量,共享内存与消息队列。我们了解了他们所提供的高级功能以及如何提供这些功能,一旦理解了这些函数,他们就会为需要进程间通信的程序提供强大的解决方案。
如果想深入体验LINUX系统的新手,也可以先下载一个方德Linux软件中心试用一下。
免费下载地址:http://www.nfs-cloud.cn:81/appCenter/open/softcenter

12-11 12:04