进程间通讯,进程间消息通信
既然我们知道了如何使用一个众所周知的管道来实现一个简单的客户机/服务器系统,我们就可以回顾一下我们的CD数据库程序,并作出相应的修改。同时,我们结合了一些信号处理,允许我们在进程中断时做一些清理动作。我们将使用带有命令行界面的dbm的以前版本,这样我们可以直接查看代码。
在我们更详细地讨论新版本的代码之前,让我们先编译这个程序。如果我们有从网站上获得的源代码,我们可以使用makefile来编译和生成服务器和客户端程序。
输入server -i使程序初始化一个新的CD数据库。
不用说,在服务器启动并运行之前,客户机不会运行。下面是makefile文件,它显示了程序是如何组合在一起的。
全部:服务器客户端
CC=cc
CFLAGS=-迂腐-墙
#用于调试,取消注释下一行
# DFLAGS=-DDEBUG_TRACE=1 -g
#我们使用dbm的位置和版本。
#这假设gdbm预装在一个标准的地方,但是我们
#将使用gdbm兼容性例程,使其模拟ndbm。
#我们这样做是因为ndbm是dbm版本中“最标准的”。
#根据您的发行版本,这些可能需要更改。
DBM公司路径=/usr/include/gdbm
DBM _图书馆_路径=/usr/lib
DBM文件=gdbm
c.o:
$(CC) $(CFLAGS) -I$(DBM公司路径)$(DFLAGS) -c $。
app_ui.o: app_ui.c cd_data.h
cd_dbm.o: cd_dbm.c cd_data.h
client _ f . o:clientif . c CD _ data . h CLI serv . h
pipe _ imp . o:pipe _ imp . c CD _ data . h CLI serv . h
server . o:server . c CD _ data . h CLI serv . h
客户端:app_ui.o clientif.o pipe_imp.o
$(CC)-o client $(d flags)app _ ui . o client if . o pipe _ imp . o
server:server . o CD _ DBM . o pipe _ imp . o
$(CC)-o server-L $(DBM _ LIB _ PATH)$(d flags)server . o CD _ DBM . o pipe _ imp . o-
l $(DBM _图书馆_文件)
清洁:
rm -f服务器客户端应用*。o *~
目标
我们的目标是将数据库处理部分与用户界面处理部分分开。我们希望同时运行一个服务器进程,但是允许多个并发客户端。同时,我们希望对现有代码做最小的改动。如果可能,我们保持现有代码不变。
为了简单起见,我们还想在程序中创建和删除管道,这样系统管理员就不需要在使用这些程序之前创建命名管道。
另外,保证我们永远不会‘等’一件事浪费CPU时间也很重要。正如我们所看到的,Linux允许我们阻塞和等待事件,而不是使用重要的资源。我们应该使用流水线的阻塞特性来确保我们可以更有效地使用CPU。简而言之,理论上,服务器应该能够等待几个小时来等待请求的到来。
在前面第7章程序的单进程版本中,我们使用了一组数据访问函数进行数据操作。它们是:
int database _ initialize(const int new _ database);
void database _ close(void);
CDC _ entry get _ CDC _ entry(const char * CD _ catalog _ ptr);
CDT _ entry get _ CDT _ entry(const char * CD _ catalog _ ptr,const int track _ no);
int add _ CDC _ entry(const CDC _ entry entry _ to _ add);
int add _ CDT _ entry(const CDT _ entry entry _ to _ add);
int del _ CDC _ entry(const char * CD _ catalog _ ptr);
int del _ CDT _ entry(const char * CD _ catalog _ ptr,const int track _ no);
CDC _ entry search _ CDC _ entry(const char * CD _ catalog _ ptr
int * first _ call _ ptr);
这些函数为区分客户端和服务器提供了一个合适的位置。
在单个进程的实现中,我们可以认为这个程序有两个部分,尽管它们是作为一个程序编译在一起的,如图13-6所示。
在客户端-服务器实现中,我们希望在程序的两个主要部分之间插入一些合理的、众所周知的管道,并提供代码。图13-7显示了我们需要的结构。
在我们的实现中,我们选择将客户机和服务器接口例程放在同一个文件pipe_imp.c中。这将把所有依赖于用于客户机/服务器实现的众所周知的管道的代码放在一个文件中。要传输的数据的格式化和打包与实现命名管道的例程相分离。程序的调用结构如图13-8所示。
文件app_ui.c、client_if.c和pipe_imp.c被编译并链接在一起以提供客户端程序。文件cd_dbm.c、server.c和pipe_imp.c被编译并链接在一起以提供服务器程序。头文件cliserv.h是一个公共定义头文件,它将两者连接在一起。
app_ui.c和cd_dbm.c这两个文件只有很少的小改动,所以原则上可以分成两个程序。因为现在程序很大,主要部分代码与之前版本相比变化不大,这里只讨论cliserv.h、client_if.c、pipe_imp.c三个文件。
我们先来看看cliserv.h。这个文件定义了客户机/服务器接口。这是客户机-服务器实现所必需的。
测试头文件cliserv.h
1下面是必需的#include头文件声明:
#包括unistd.h
#包含stdlib.h
#包含stdio.h
#包含fcntl.h
#包括限额. h
#包含sys/types.h
#包含系统/统计信息
然后我们定义管道这个名称。我们对服务器使用一个管道,对所有客户机使用另一个管道。因为可能有多个客户端,所以客户端在其名称中组合了一个进程ID,以确保其管道是唯一的:
#定义服务器管道"/tmp/服务器管道"
# define CLIENT _ PIPE "/tmp/CLIENT _ % d _ PIPE "
#定义ERR_TEXT_LEN 80
3我们将命令实现为枚举类型,而不是#define定义。
这是一个很好的方法,可以让编译器做更多的类型检查工作,同时也有助于程序的调试,因为很多调试器可以显示枚举常量的名称,但是不能显示#define指令定义的名称。
第一个typdef指定要发送到服务器的请求的类型;第二个指定了服务器对客户端的响应类型。
typedef枚举{
s_create_new_database=0,
s_get_cdc_entry,
s_get_cdt_entry,
s_add_cdc_entry,
s_add_cdt_entry,
s_del_cdc_entry,
s_del_cdt_entry,
s_find_cdc_entry
}客户端_请求_ e;
typedef枚举{
r_success=0,
r _失败,
更多信息
}服务器_响应_ e;
4接下来,我们声明一个结构,它构成了一个可以在两个进程之间双向传递的消息。
typedef结构{
pid _ t client _ pid
客户端请求请求;
server_response_e响应;
CDC _ entry;
cdt_entry cdt_entry_data
char error _ TEXT[ERR _ TEXT _ l EN 1];
} message _ db _ t;
5.最后,我们了解了用于数据传输的管道接口函数,该函数在pipe_imp.c中实现。在第一个和第二个块中,这些函数分别被划分为服务器端和客户端函数:
int server _ starting(void);
void server _ ending(void);
int read _ request _ from _ client(message _ db _ t * rec _ ptr);
int start _ resp _ to _ client(const message _ db _ t mess _ to _ send);
int send _ resp _ to _ client(const message _ db _ t mess _ to _ send);
void end _ resp _ to _ client(void);
int client _ starting(void);
void client _ ending(void);
int send _ mess _ to _ server(message _ db _ t mess _ to _ send);
int start _ resp _ from _ server(void);
int read _ resp _ from _ server(message _ db _ t * rec _ ptr);
void end _ resp _ from _ server(void);
我们接下来的讨论分为对pipe_imp.c中定义的客户端接口函数以及服务器端和客户端函数的详细讨论,我们将讨论必要的源代码。
客户界面功能
现在我们来讨论client_if.c他提供了一个虚拟版本的数据库访问例程。他将请求编码到message_db_t结构中,然后使用pipe_imp.c中的例程将这些请求传递给服务器。这将允许我们对原来的app_ui.c做最小的改动
测试客户解释器
1这个文件实现了cd_data中定义的9个数据库函数. h他通过将请求传递给服务器,并从函数返回服务器响应来实现操作,类似于中间人。该文件以#include和常量定义开头:
#定义_ POSIX _源
#包括unistd.h
#包含标准库
#包含标准视频
#包含fcntl.h
#包括限额。h
#包含sys/types.h
#包含系统/统计信息
#include cd_data.h
#包含" cliserv.h "
2静态变量mypid减少了所需要的getpid函数的调用次数。我们使用一个局部函数,read_one_response,来减少重复的代码:
静态pid _ t mypid
static int read _ one _ response(message _ db _ t * rec _ ptr);
3数据库_初始化与关闭例程仍然被调用,但是现在分别用来初始化管道接口的客户端和当客户端退出时移除多余的有名管道。
int database_initialize(常量(同Internationalorganizations)国际组织新数据库)
{
如果(!client_starting())返回(0);
mypid=getpid();
返回(1);
} /*数据库初始化*/
void database_close(void) {
client _ ending();
}
4 get_cdc_entry例被调用用来在指定一个激光唱片类别标题的情况下由数据中获取一个类别实体。在这里我们将请求编码进消息数据库结构中,并且将其传递给服务器。然后我们读取返回的响应放入另一个不同的消息数据库结构中。如果找到一个实体,他就会被作为一个cdc_entry结构被包含进入消息数据库结构中,所以我们需要介绍结构的相关部分:
CDC _ entry get _ CDC _ entry(const char * CD _ catalog _ ptr)
{
cdc _ entry ret _ val
消息_数据库_邮件_发送
消息_数据库_ t消息_ ret
ret _ val。目录[0]=/0 ;
mess _ send.client _ pid=mypid
乱_发。request=s _ get _ CDC _ entry
strcpy(mess _ send。CDC _ entry _ data。目录,CD _ catalog _ ptr);
if(send _ mess _ to _ server(mess _ send))。
if(read _ one _ response(mess _ ret)){
if(mess _ ret。response==r _ success){
ret _ val=mess _ ret。CDC _ entry _ data
}否则{
fprintf(stderr," %s ",mess _ ret。error _ text);
}
}否则{
fprintf(stderr,"服务器未能响应/n ");
}
}否则{
fprintf(stderr,"服务器不接受请求/n ");
}
ret(ret _ val);
}
5下面是我们用来避免重复代码的阅读_一个_响应函数源码:
static int read _ one _ response(message _ db _ t * rec _ ptr){
int return _ code=0;
如果(!rec _ ptr)return(0);
if (start_resp_from_server()) {
if(read _ resp _ from _ server(rec _ ptr)){
return _ code=1;
}
end _ resp _ from _ server();
}
返回(返回_代码);
}
6 其他的get_xxx,del_xxx与add_xxx例程的实现方式与get_cdc_entry函数类似,为了完整,我们在这里进行介绍。首先是用来读取激光唱片音轨的函数源码:
cdt_entry get_cdt_entry(常量char *cd_catalog_ptr常量int track_no)
{
cdt _ entry返回值
消息_数据库_邮件_发送
消息_数据库_ t消息_ ret
ret _ val。目录[0]=/0 ;
mess _ send.client _ pid=mypid
乱_发。request=s _ get _ CDT _ entry
strcpy(mess _ send。CDT _ entry _ data。目录,CD _ catalog _ ptr);
乱_发。CDT _ entry _ data。track _ no=track _ no
if(send _ mess _ to _ server(mess _ send))。
if(read _ one _ response(mess _ ret)){
if(mess _ ret。response==r _ success){
ret _ val=mess _ ret。CDT _ entry _ data
}否则{
fprintf(stderr," %s ",mess _ ret。error _ text);
}
}否则{
fprintf(stderr,"服务器未能响应/n ");
}
}否则{
fprintf(stderr,"服务器不接受请求/n ");
}
ret(ret _ val);
}
七下面是两个是用来添加数据的函数,第一个向类别中添加,而第二个向音轨数据中添加:
int add _ CDC _ entry(const CDC _ entry entry _ to _ add)
{
消息_数据库_邮件_发送
消息_数据库_ t消息_ ret
mess _ send.client _ pid=mypid
乱_发。request=s _ add _ CDC _ entry
乱_发。CDC _ entry _ data=entry _ to _ add
if(send _ mess _ to _ server(mess _ send))。
if(read _ one _ response(mess _ ret)){
if(mess _ ret。response==r _ success){
返回(1);
}否则{
fprintf(stderr," %s ",mess _ ret。error _ text);
}
}否则{
fprintf(stderr,"服务器未能响应/n ");
}
}否则{
fprintf(stderr,"服务器不接受请求/n ");
}
return(0);
}
int add _ CDT _ entry(const CDT _ entry entry _ to _ add)
{
消息_数据库_邮件_发送
消息_数据库_ t消息_ ret
mess _ send.client _ pid=mypid
乱_发。request=s _ add _ CDT _ entry
乱_发。CDT _ entry _ data=entry _ to _ add
if(send _ mess _ to _ server(mess _ send))。
if(read _ one _ response(mess _ ret)){
if(mess _ ret。response==r _ success){
返回(1);
}否则{
fprintf(stderr," %s ",mess _ ret。error _ text);
}
}否则{
fprintf(stderr,"服务器未能响应/n ");
}
}否则{
fprintf(stderr,"服务器不接受请求/n ");
}
return(0);
}
8最后是两个用来数据删除的函数:
int del _ CDC _ entry(const char * CD _ catalog _ ptr)
{
消息_数据库_邮件_发送
消息_数据库_ t消息_ ret
mess _ send.client _ pid=mypid
乱_发。request=s _ del _ CDC _ entry
strcpy(mess _ send。CDC _ entry _ data。目录,CD _ catalog _ ptr);
if(send _ mess _ to _ server(mess _ send))。
if(read _ one _ response(mess _ ret)){
if(mess _ ret。response==r _ success){
返回(1);
}否则{
fprintf(stderr," %s ",mess _ ret。error _ text);
}
}否则{
fprintf(stderr,"服务器未能响应/n ");
}
}否则{
fprintf(stderr,"服务器不接受请求/n ");
}
return(0);
}
int del _ CDT _ entry(const char * CD _ catalog _ ptr,const int track_no)
{
消息_数据库_邮件_发送
消息_数据库_ t消息_ ret
mess _ send.client _ pid=mypid
乱_发。request=s _ del _ CDT _ entry
strcpy(mess _ send。CDT _ entry _ data。目录,CD _ catalog _ ptr);
乱_发。CDT _ entry _ data。track _ no=track _ no
if(send _ mess _ to _ server(mess _ send))。
if(read _ one _ response(mess _ ret)){
if(mess _ ret。response==r _ success){
返回(1);
}否则{
fprintf(stderr," %s ",mess _ ret。error _ text);
}
}否则{
fprintf(stderr,"服务器未能响应/n ");
}
}否则{
fprintf(stderr,"服务器不接受请求/n ");
}
return(0);
}