进程间通信(八)
搜索数据库
在CD关键字上的搜索比较复杂。函数的用户希望一旦调用就启动一个搜索。我们在第7章通过将在第一次调用上的*first_call_ptr设置为true并且函数返回第一个匹配结果来满足这种需求。在接下来的搜索函数调用中,*first_call_ptr设置为false,从而会返回更多的匹配,每次调用返回一个匹配结果。
现在我们已经将程序分为两个进程,我们不能再允许搜索在服务器端一次处理一个实体,因为另一个不同的客户端也许会由服务器请求一个不同的搜索,而我们的搜索仍在处理之中。我们不能使得服务器端为每一个客户端单独存储搜索内容(搜索已经进行到哪里),因为当一个用户找到他们要找的CD关键值时或是客户端出现问题,客户端就可以简单的停止搜索。
我们或者可以改变搜索执行的方式,或者是,正如我们在这里所选择的,在接口例程中隐藏复杂性。我们所做的就是使得服务器返回所有可能的搜索匹配,然后将他们存储在一个临时文件中直到客户端请求他们。
试验--查找
1 这个函数看起更为复杂,因为他调用我们会在下一节讨论的三个管道函数:send_mess_to_server,start_resp_from_server与read_resp_from_server。
cdc_entry search_cdc_entry(const char *cd_catalog_ptr, int *first_call_ptr)
{
message_db_t mess_send;
message_db_t mess_ret;
static FILE *work_file = (FILE *)0;
static int entries_matching = 0;
cdc_entry ret_val;
ret_val.catalog[0] = ‘/0’;
if (!work_file && (*first_call_ptr == 0)) return(ret_val);
2 下面是搜索的第一个调用,在这里*first_call_ptr设置为true。为了防止我们忘记,立即将其设置为false。在这个函数中创建一个work_file并且初始化客户端消息结构。
if (*first_call_ptr) {
*first_call_ptr = 0;
if (work_file) fclose(work_file);
work_file = tmpfile();
if (!work_file) return(ret_val);
mess_send.client_pid = mypid;
mess_send.request = s_find_cdc_entry;
strcpy(mess_send.cdc_entry_data.catalog, cd_catalog_ptr);
3 接下是一个三层的条件测试。如果消息成功的发送到服务器,客户端会等待服务器的响应。当服务器的read操作成功,搜索匹配就会被记入work_file中,而相应的entries_matching也会增加。
if (send_mess_to_server(mess_send)) {
if (start_resp_from_server()) {
while (read_resp_from_server(&mess_ret)) {
if (mess_ret.response == r_success) {
fwrite(&mess_ret.cdc_entry_data, sizeof(cdc_entry), 1, work_file);
entries_matching++;
} else {
break;
}
} /* while */
} else {
fprintf(stderr, “Server not responding/n”);
}
} else {
fprintf(stderr, “Server not accepting requests/n”);
}
4 下面的测试检测搜索是否成功。然后fseek调用会将work_file设置为数据将要写入的下一个位置。
if (entries_matching == 0) {
fclose(work_file);
work_file = (FILE *)0;
return(ret_val);
}
(void)fseek(work_file, 0L, SEEK_SET);
5 如果这并不是第一次使用特定的搜索模式调用搜索函数,下面的代码会检测是否还有余下的匹配。最后,下一个匹配项会被读入ret_val结构中。前面的检测认为存在一个匹配项。
} else {
/* not *first_call_ptr */
if (entries_matching == 0) {
fclose(work_file);
work_file = (FILE *)0;
return(ret_val);
}
}
fread(&ret_val, sizeof(cdc_entry), 1, work_file);
entries_matching—;
return(ret_val);
}
服务器接口
就如客户端有一个对app_ui.c程序的一个接口,服务器端也需要一个程序来控制(重命名的)cd_access.c,现在是cd_dbm.c。服务器的main函数如下。
试验--server.c
1 我们在程序的开始处声明了几个全局变量,一个process_command函数原型,以及一个信号捕获函数来保证一个干净的退出。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include “cd_data.h”
#include “cliserv.h”
int save_errno;
static int server_running = 1;
static void process_command(const message_db_t mess_command);
void catch_signals()
{
server_running = 0;
}
2 现在我们来了解一下main函数。在检测信号捕获例程正确之后,程序检测我们是否在命令行传递了一个-i选项。如果我们传递了,程序就会创建一个新的数据库。如果cd_dbm.c中的database_initialize例程失败,就会显示一个错误消息。如果一切正常而且服务器正在运行,由客户端来的请求就会被传递给process_command函数,这个函数我们将会稍后介绍。
int main(int argc, char *argv[]) {
struct sigaction new_action, old_action;
message_db_t mess_command;
int database_init_type = 0;
new_action.sa_handler = catch_signals;
sigemptyset(&new_action.sa_mask);
new_action.sa_flags = 0;
if ((sigaction(SIGINT, &new_action, &old_action) != 0) ||
(sigaction(SIGHUP, &new_action, &old_action) != 0) ||
(sigaction(SIGTERM, &new_action, &old_action) != 0)) {
fprintf(stderr, “Server startup error, signal catching failed/n”);
exit(EXIT_FAILURE);
}
if (argc > 1) {
argv++;
if (strncmp(“-i”, *argv, 2) == 0) database_init_type = 1;
}
if (!database_initialize(database_init_type)) {
fprintf(stderr, “Server error:-/
could not initialize database/n”);
exit(EXIT_FAILURE);
}
if (!server_starting()) exit(EXIT_FAILURE);
while(server_running) {
if (read_request_from_client(&mess_command)) {
process_command(mess_command);
} else {
if(server_running) fprintf(stderr, “Server ended - can not /
read pipe/n”);
server_running = 0;
}
} /* while */
server_ending();
exit(EXIT_SUCCESS);
}
3 所有的客户端消息都会被传递给process_command函数,在那里他们会被传递给一个case语句,从而执行cd_dbm.c中的正确调用。
static void process_command(const message_db_t comm)
{
message_db_t resp;
int first_time = 1;
resp = comm; /* copy command back, then change resp as required */
if (!start_resp_to_client(resp)) {
fprintf(stderr, “Server Warning:-/
start_resp_to_client %d failed/n”, resp.client_pid);
return;
}
resp.response = r_success;
memset(resp.error_text, ‘/0’, sizeof(resp.error_text));
save_errno = 0;
switch(resp.request) {
case s_create_new_database:
if (!database_initialize(1)) resp.response = r_failure;
break;
case s_get_cdc_entry:
resp.cdc_entry_data =
get_cdc_entry(comm.cdc_entry_data.catalog);
break;
case s_get_cdt_entry:
resp.cdt_entry_data =
get_cdt_entry(comm.cdt_entry_data.catalog,
comm.cdt_entry_data.track_no);
break;
case s_add_cdc_entry:
if (!add_cdc_entry(comm.cdc_entry_data)) resp.response =
r_failure;
break;
case s_add_cdt_entry:
if (!add_cdt_entry(comm.cdt_entry_data)) resp.response =
r_failure;
break;
case s_del_cdc_entry:
if (!del_cdc_entry(comm.cdc_entry_data.catalog)) resp.response
= r_failure;
break;
case s_del_cdt_entry:
if (!del_cdt_entry(comm.cdt_entry_data.catalog,
comm.cdt_entry_data.track_no)) resp.response = r_failure;
break;
case s_find_cdc_entry:
do {
resp.cdc_entry_data =
search_cdc_entry(comm.cdc_entry_data.catalog,
&first_time);
if (resp.cdc_entry_data.catalog[0] != 0) {
resp.response = r_success;
if (!send_resp_to_client(resp)) {
fprintf(stderr, “Server Warning:-/
failed to respond to %d/n”, resp.client_pid);
break;
}
} else {
resp.response = r_find_no_more;
}
} while (resp.response == r_success);
break;
default:
resp.response = r_failure;
break;
} /* switch */
sprintf(resp.error_text, “Command failed:/n/t%s/n”,
strerror(save_errno));
if (!send_resp_to_client(resp)) {
fprintf(stderr, “Server Warning:-/
failed to respond to %d/n”, resp.client_pid);
}
end_resp_to_client();
return;
}
在我们了解实际的管道实现之前,让我们来讨论一下在客户端与服务器进程之间传递数据需要发生的事件序列。图13-9显示所启动的客户端与服务器进程以及在处理命令与响应时双方是如何循环的。
在这个实现中,形势有一些困难,因为对于一个搜索请求,客户端向服务器传递一个单一命令,然后希望由服务器接收一个或多个响应。这会导致其他额外的一些复杂性,主要在客户端。