CD数据库程序
现在我们已经了解了我们可以如何使用有名管道来实现一个简单的客户端/服务器系统,我们可以重新查看我们的CD数据库程序并且进行相应的修改。我们同时组合了一些信号处理从而允许我们在进程被中断时进行一些清理动作。我们会使用我们前面的具有一个命令行接口的dbm版本,从而进可能直接的查看代码。
在我们更为详细的讨论新版本的代码之前,让我们先编译这个程序。如果我们有由Web站点所获得的源代码,就可以使用makefile来编译生成server与client程序。
输入server -i使得程序初始化一个新的CD数据库。
无需多言,客户端直到服务器启动并运行时才运行。下面是makefile文件,显示程序是如何组合在一起的。
all: server client
CC=cc
CFLAGS= -pedantic -Wall
# For debugging un-comment the next line
# DFLAGS=-DDEBUG_TRACE=1 -g
# Where, and which version, of dbm are we using.
# This assumes gdbm is pre-installed in a standard place, but we are
# going to use the gdbm compatibility routines, that make it emulate ndbm.
# We do this because ndbm is the ‘most standard’ of the dbm versions.
# Depending on your distribution, these may need changing.
DBM_INC_PATH=/usr/include/gdbm
DBM_LIB_PATH=/usr/lib
DBM_LIB_FILE=gdbm
.c.o:
$(CC) $(CFLAGS) -I$(DBM_INC_PATH) $(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 cliserv.h
pipe_imp.o: pipe_imp.c cd_data.h cliserv.h
server.o: server.c cd_data.h cliserv.h
client: app_ui.o clientif.o pipe_imp.o
$(CC) -o client $(DFLAGS) app_ui.o clientif.o pipe_imp.o
server: server.o cd_dbm.o pipe_imp.o
$(CC) -o server -L$(DBM_LIB_PATH) $(DFLAGS) server.o cd_dbm.o pipe_imp.o -
l$(DBM_LIB_FILE)
clean:
rm -f server client_app *.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头文件声明:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
2 然后我们定义有名管道。我们使用一个管道用于服务器,另一个管道用于所有的客户端。因为也许会有多个客户端,客户端在其名字中组合一个进程ID来保证他的管道是唯一的:
#define SERVER_PIPE “/tmp/server_pipe”
#define CLIENT_PIPE “/tmp/client_%d_pipe”
#define ERR_TEXT_LEN 80
3 我们将命令实现为枚举类型,而不是#define定义。
这是一个好办法,允许编译器进行更多的类型检测工作,同时有助于程序的调试,因为许多调试器可以显示枚举常量的名字,但是不能显示由#define指令所定义的名字。
第一个typdef指定了将要发送给服务器的请求类型;第二个指定了服务器向客户端的响应类型。
typedef enum {
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
} client_request_e;
typedef enum {
r_success = 0,
r_failure,
r_find_no_more
} server_response_e;
4 接下来我们声明了一个构成可以在两个进程之间双向传递的消息的结构。
typedef struct {
pid_t client_pid;
client_request_e request;
server_response_e response;
cdc_entry cdc_entry_data;
cdt_entry cdt_entry_data;
char error_text[ERR_TEXT_LEN + 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.h中定义的9个数据库函数。他通过向服务器传递请求并且由函数中返回服务器响应来实现操作,类似于一个中间人。这个文件由#include与常量定义开始:
#define _POSIX_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "cd_data.h"
#include "cliserv.h"
2 静态变量mypid减少了所需要的getpid函数的调用次数。我们使用一个局部函数,read_one_response,来减少重复的代码:
static pid_t mypid;
static int read_one_response(message_db_t *rec_ptr);
3 database_initialize与close例程仍然被调用,但是现在分别用来初始化管道接口的客户端和当客户端退出时移除多余的有名管道。
int database_initialize(const int new_database)
{
if (!client_starting()) return(0);
mypid = getpid();
return(1);
} /* database_initialize */
void database_close(void) {
client_ending();
}
4 get_cdc_entry例被调用用来在指定一个CD类别标题的情况下由数据中获取一个类别实体。在这里我们将请求编码进message_db_t结构中,并且将其传递给服务器。然后我们读取返回的响应放入另一个不同的message_db_t结构中。如果找到一个实体,他就会被作为一个cdc_entry结构被包含进入message_db_t结构中,所以我们需要介绍结构的相关部分:
cdc_entry get_cdc_entry(const char *cd_catalog_ptr)
{
cdc_entry ret_val;
message_db_t mess_send;
message_db_t mess_ret;
ret_val.catalog[0] = ‘\0’;
mess_send.client_pid = mypid;
mess_send.request = s_get_cdc_entry;
strcpy(mess_send.cdc_entry_data.catalog, 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;
} else {
fprintf(stderr, “%s”, mess_ret.error_text);
}
} else {
fprintf(stderr, “Server failed to respond\n”);
}
} else {
fprintf(stderr, “Server not accepting requests\n”);
}
return(ret_val);
}
5 下面是我们用来避免重复代码的read_one_response函数源码:
static int read_one_response(message_db_t *rec_ptr) {
int return_code = 0;
if (!rec_ptr) return(0);
if (start_resp_from_server()) {
if (read_resp_from_server(rec_ptr)) {
return_code = 1;
}
end_resp_from_server();
}
return(return_code);
}
6 其他的get_xxx,del_xxx与add_xxx例程的实现方式与get_cdc_entry函数类似,为了完整,我们在这里进行介绍。首先是用来读取CD音轨的函数源码:
cdt_entry get_cdt_entry(const char *cd_catalog_ptr, const int track_no)
{
cdt_entry ret_val;
message_db_t mess_send;
message_db_t mess_ret;
ret_val.catalog[0] = ‘\0’;
mess_send.client_pid = mypid;
mess_send.request = s_get_cdt_entry;
strcpy(mess_send.cdt_entry_data.catalog, cd_catalog_ptr);
mess_send.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;
} else {
fprintf(stderr, “%s”, mess_ret.error_text);
}
} else {
fprintf(stderr, “Server failed to respond\n”);
}
} else {
fprintf(stderr, “Server not accepting requests\n”);
}
return(ret_val);
}
7 下面是两个是用来添加数据的函数,第一个向类别中添加,而第二个向音轨数据中添加:
int add_cdc_entry(const cdc_entry entry_to_add)
{
message_db_t mess_send;
message_db_t mess_ret;
mess_send.client_pid = mypid;
mess_send.request = s_add_cdc_entry;
mess_send.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) {
return(1);
} else {
fprintf(stderr, “%s”, mess_ret.error_text);
}
} else {
fprintf(stderr, “Server failed to respond\n”);
}
} else {
fprintf(stderr, “Server not accepting requests\n”);
}
return(0);
}
int add_cdt_entry(const cdt_entry entry_to_add)
{
message_db_t mess_send;
message_db_t mess_ret;
mess_send.client_pid = mypid;
mess_send.request = s_add_cdt_entry;
mess_send.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) {
return(1);
} else {
fprintf(stderr, “%s”, mess_ret.error_text);
}
} else {
fprintf(stderr, “Server failed to respond\n”);
}
} else {
fprintf(stderr, “Server not accepting requests\n”);
}
return(0);
}
8 最后是两个用来数据删除的函数:
int del_cdc_entry(const char *cd_catalog_ptr)
{
message_db_t mess_send;
message_db_t mess_ret;
mess_send.client_pid = mypid;
mess_send.request = s_del_cdc_entry;
strcpy(mess_send.cdc_entry_data.catalog, cd_catalog_ptr);
if (send_mess_to_server(mess_send)) {
if (read_one_response(&mess_ret)) {
if (mess_ret.response == r_success) {
return(1);
} else {
fprintf(stderr, “%s”, mess_ret.error_text);
}
} else {
fprintf(stderr, “Server failed to respond\n”);
}
} else {
fprintf(stderr, “Server not accepting requests\n”);
}
return(0);
}
int del_cdt_entry(const char *cd_catalog_ptr, const int track_no)
{
message_db_t mess_send;
message_db_t mess_ret;
mess_send.client_pid = mypid;
mess_send.request = s_del_cdt_entry;
strcpy(mess_send.cdt_entry_data.catalog, cd_catalog_ptr);
mess_send.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) {
return(1);
} else {
fprintf(stderr, “%s”, mess_ret.error_text);
}
} else {
fprintf(stderr, “Server failed to respond\n”);
}
} else {
fprintf(stderr, “Server not accepting requests\n”);
}
return(0);
}
分享到:
相关推荐
c语言盒子接球游戏源码
YOLOv8-streamlit-app软件,使用yolov8做的物体识别语义分割姿态检测,使用streamlit做的显示界面。下载即可运行,可做毕业设计。
MATLAB与计算物理课程 (第十周)第三章线性方程组的迭代法 共70页.pptx
在线日语培训平台 SSM毕业设计 附带论文 启动教程:https://www.bilibili.com/video/BV1GK1iYyE2B
c语言学生信息系统
Java大学生体质检测管理系统源码 大学生体质管理平台源码 源码描述 作为对大学生健康的监测的信息系统,其主要的工作是对相关数据的收集预测和给予正确的评价。 因此,该系统的设计目标则主要包括以下几个方面: 1)可应用于对学生相关健康数据的收集、存储、传递、维护和加工; 2)通过系统可对学生的相关身体健康情况进行科学的分析,并给予学生老师给出比较客观的评价指标。 3)具有一定的延展性可根据体育教学的需要,添加其他功能模块的系统。
技术资料分享DHT11很好的技术资料.zip
JNI 简介与实现
# 基于PyTorch框架的医学图像分割系统 ## 项目简介 本项目是一个基于PyTorch框架的医学图像分割系统,旨在利用深度学习模型对医学图像进行精确分割。系统中包含了用于图像分割的UNet模型,以及用于处理医学图像数据集的脚本和工具。项目还包含了用于训练模型的脚本,以及用于评估模型性能的指标计算和可视化工具。 ## 主要特性和功能 1. UNet模型实现项目中使用了UNet架构,用于对医学图像进行分割。该模型可根据不同的backbone(如VGG或ResNet)进行初始化,并支持冻结和解冻backbone的参数,以适应不同的训练需求。 2. 医学图像数据集处理项目提供了处理医学图像数据集的脚本,包括从原始数据中提取标注信息、生成训练、验证和测试集,以及进行数据增强和预处理等操作。
SQLite数据库工具
3d打印机,Prusa3D
c语言五子棋源码
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
# 基于Spring Boot和MyBatis的宿舍管理系统 ## 项目简介 本项目是一个基于Spring Boot和MyBatis框架开发的宿舍管理系统,旨在为学生、宿管和后勤人员提供一个高效的管理平台。系统支持用户管理、房间管理、楼宇管理、维修申请等功能,并具备人性化的宿舍分配和可视化的账单管理。 ## 项目的主要特性和功能 1. 用户管理系统中的用户分为学生、宿管和后勤三类,每类用户拥有不同的操作权限。 2. 房间管理支持房间的创建、删除、更新和查询操作,以及房间容量的调整。 3. 楼宇管理支持楼宇的创建、删除、更新和查询操作,以及楼宇入住率和性别信息的统计。 4. 维修申请学生可以提交维修申请,宿管和后勤人员可以管理和处理这些申请。 5. 账单管理支持账单的创建、删除、更新和查询操作,以及Excel文件的上传和数据导入。 6. 宿舍分配系统提供人性化的宿舍分配功能,支持根据学生的意向进行分配。
下载解压后,得到一个tcping.exe ,将tcping.exe 放到C盘Windows路径下。
JNI编程指南
技术资料分享FAT32文件系统详解很好的技术资料.zip
个人日常总结,待整理 杂乱的笔记
Texiaodemo