Текст книги "Основы программирования в Linux"
Автор книги: Нейл Мэтью
Соавторы: Ричард Стоунс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 50 (всего у книги 67 страниц)
Заканчивая обсуждение каналов FIFO, давайте рассмотрим возможность построения очень простого клиент-серверного приложения, применяющего именованные каналы. Вы хотите, чтобы один серверный процесс принимал запросы, обрабатывал их и возвращал результирующие данные запрашивающей стороне – клиенту.
Вам нужно разрешить множественным клиентским процессам отправлять данные серверу. Для простоты предположим, что данные, которые нужно обработать, можно разбить на блоки, каждый из которых меньше PIPE_BUF
байтов. Конечно, реализовать такую систему можно разными способами, но мы рассмотрим только один, как иллюстрацию применения именованных каналов.
Поскольку сервер будет обрабатывать только один блок данных в каждый момент времени, кажется логичным создать один канал FIFO, который читается сервером и в который записывают всё клиенты. Если открыть FIFO в блокирующем режиме, сервер и клиенты будут при необходимости блокироваться.
Возвращать обработанные данные клиентам немного сложнее. Вам придется организовать второй канал для возвращаемых данных, один для каждого клиента. Если передавать идентификатор (PID) процесса-клиента в исходных данных, отправляемых на сервер, обе стороны смогут использовать его для генерации уникального имени канала с возвращаемыми данными.
Выполните упражнение 13.13.
Упражнение 13.13. Пример клиент-серверного приложения
1. Прежде всего, вам нужен заголовочный файл client.h, в котором определены данные, общие для серверных и клиентских программ. В приложение также для удобства включены требуемые системные заголовочные файлы.
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_FIFO_NAME «/tmp/serv_fifo»
#define CLIENT_FIFO_NAME «/tmp/cli_%d_fifo»
#define BUFFER_SIZE 20
struct data_to_pass_st {
pid_t client_pid;
char some_data[BUFFER_SIZE – 1];
};
2. Теперь займемся серверной программой server.c. В этом разделе вы создаете и затем открываете канал сервера. Он задается в режиме "только для чтения" и с блокировкой. После засыпания (из демонстрационных соображений) сервер читает данные от клиента, у которого есть структура типа data_to_pass_st
.
#include «client.h»
#include
int main() {
int server_fifo_fd, client fifo_fd;
struct data_to_pass_st my_data;
int read_res;
char client_fifo[256];
char *tmp_char_ptr;
mkfifo(SERVER_FIFO_NAME, 0777);
server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);
if (server_fifo_fd == -1) {
fprintf(stderr, «Server fifo failuren»);
exit(EXIT_FAILURE);
}
sleep(10); /* для целей демонстрации разрешает клиентам создать очередь */
do {
read_res = read(server_fifo_fd, &my_data, sizeof(my_data));
if (read res > 0) {
3. На следующем этапе вы выполняете некоторую обработку данных, только что полученных от клиента: преобразуете все символы в некоторых данных в прописные и соединяете CLIENT_FIFO_NAME
с полученным идентификатором client_pid
.
tmp_char_ptr = my_data.some_data;
while (*tmp_char_ptr) {
*tmp_char_ptr = toupper(* tmp_char_ptr);
tmp_char_ptr++;
}
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
4. Далее отправьте обработанные данные назад, открыв канал клиентской программы в режиме "только для записи" и с блокировкой. В заключение закройте серверный FIFO с помощью закрытия файла и отсоединения FIFO.
client_fifo_fd = open(client_fifo, O_WRONLY);
if (client_fifo_fd ! = -1) {
write(client_fifo_fd, &my_data, sizeof(my_data));
close(client_fifo_fd);
}
}
} while (read_res > 0);
close(server_fifo_fd);
unlink(SERVER_FIFO_NAME);
exit(EXIT_SUCCESS);
}
5. Далее приведена клиентская программа client.с. В первой части этой программы FIFO сервера, если он уже существует, открывается как файл. Далее программа получает идентификатор собственного процесса, который формирует некие данные, которые будут отправляться на сервер. Создается FIFO клиента, подготовленный для следующего раздела.
#include «client.h»
#include
int main() {
int server_fifo_fd, client_fifo_fd;
struct data_to_pass_st my_data;
int times_to_send;
char client_fifo[256];
server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);
if (server_fifo_fd == -1) {
fprintf (stderr, «Sorry, no servern»);
exit(EXIT_FAILURE);
}
my_data.client_pid = getpid();
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
if (mkfifo(client_fifo, 0777) == -1) {
fprintf(stderr, «Sorry, can't make %sn», client_fifo);
exit(EXIT_FAILURE);
}
6. В каждом из пяти проходов цикла клиентские данные отправляются на сервер. Далее клиентский FIFO открывается (в режиме "только для чтения" с блокировкой) и данные считываются обратно. В конце серверный FIFO закрывается, а клиентский FIFO удаляется из файловой системы.
for (times_to_send = 0; times_to_send < 5; times_to_send++) {
sprintf(my_data.some_data, «Hello from %d», my_data.client_pid);
printf("%d sent %s, ", my_data.client_pid, my_data.some_data);
write(server_fifo_fd, &my_data, sizeof(my_data));
client_fifo_fd = open(client_fifo, O_RDONLY);
if (client_fifo_fd != -1) {
if (read(client_fifo_fd, &my_data, sizeof(my_data)) > 0) {
printf(«received: %sn», my_data.some_data);
}
close(client_fifo_fd);
}
}
close(server_fifo_fd);
unlink(client_fifo);
exit(EXIT_SUCCESS);
}
Для тестирования этого приложения вам необходимо запустить единственную копию сервера и несколько клиентов. Для того чтобы запустить их приблизительно в одно и то же время, примените следующие команды командной оболочки.
$ ./server &
$ for i in 1 2 3 4 5
do
./client &
done
$
Они запускают один серверный процесс и пять клиентских. Вывод клиентских программ, отредактированный для краткости, выглядит следующим образом:
531 sent Hello from 531, received: HELLO FROM 531
532 sent Hello from 532, received: HELLO FROM 532
529 sent Hello from 529, received: HELLO FROM 529
530 sent Hello from 530, received: HELLO FROM 530
531 sent Hello from 531, received: HELLO FROM 531
532 sent Hello from 532, received: HELLO FROM 532
Как видно из данного вывода, запросы разных клиентов перемежаются, но каждый клиент получает соответствующим образом обработанные и возвращаемые ему данные. Имейте в виду, что вы можете увидеть или не увидеть чередование запросов, т.к. порядок получения клиентских запросов может меняться от машины к машине и даже в разных сеансах работы приложения на одной машине.
Как это работает
Теперь мы обсудим последовательность клиентских и серверных операций во взаимодействии, чего не делали до сих пор.
Сервер создает свой канал FIFO в режиме "только чтение" и блокируется. Он делает это до тех пор, пока первый клиентский процесс не подсоединится, открыв тот же FIFO для записи. В этот момент серверный процесс разблокируется и выполняется вызов sleep
, поэтому вызовы write
клиентов образуют очередь. (В реальном приложении вызов sleep
может быть удален, мы применяем его только чтобы продемонстрировать корректное функционирование программы с множественными одновременно действующими клиентами.)
Между тем, после того как клиентский процесс открыл серверный канал FIFO, он создает собственный FIFO с уникальным именем для считывания данных с сервера. Только после этого клиент записывает данные на сервер (причем, если канал полон или сервер все еще спит, клиентская программа блокируется) и затем блокирует для вызова read
свой собственный канал FIFO, ожидая ответа.
Получив данные от клиента, сервер обрабатывает их, открывает клиентский канал для записи и записывает в него данные, что снимает блокировку клиентского процесса. Когда клиент разблокирован, он может читать из своего канала данные, записанные туда сервером.
Процесс повторяется полностью до тех пор, пока последний клиент не закроет канал сервера, вызывая аварийное завершение серверного вызова read (возвращение 0), поскольку ни у одного процесса нет серверного канала, открытого для записи. Если бы это был реальный серверный процесс, вынужденный ожидать будущих клиентов, возможно, вам пришлось бы изменить его, выбрав одно из двух:
□ открыть файловый дескриптор собственного серверного канала, чтобы вызов read
всегда его блокировал, а не возвращал 0;
□ закрыть и повторно открыть серверный канал, когда read
вернет 0 байтов, чтобы серверный процесс блокировался вызовом open
, ожидая клиента, так, как он это делал, стартуя первый раз.
Оба эти метода проиллюстрированы в новом варианте приложения для работы с базой данных компакт-дисков, использующем именованные каналы.
Приложение для работы с базой данных компакт-дисков
Теперь, зная, как применять именованные каналы для реализации простой клиент-серверной системы, вы можете пересмотреть приложение для работы с базой данных компакт-дисков и соответствующим образом переработать его. Вы включите в него также некоторую обработку сигналов, позволяющую выполнить кое-какие действия по наведению порядка при прерывании процесса. Будет использоваться более ранняя версия приложения с dbm и интерфейсом командной строки, чтобы исходный текст программы был максимально простым и понятным.
Прежде чем подробно рассматривать эту новую версию, необходимо откомпилировать приложение. Если вы взяли исходный код с Web-сайта, примените make-файл для его компиляции и получения серверной и клиентской программ.
Примечание
Как было показано ранее в главе 7, в различных дистрибутивах файлы dbm именуются и устанавливаются немного по-разному. Если предоставленные файлы не компилируются в вашем дистрибутиве, вернитесь к главе 7 и поищите сведения об именах и местонахождении файлов dbm.
Выполнение команды server -i
позволяет программе инициализировать новую базу данных компакт-дисков.
Нет нужды говорить о том, что клиент не выполнится, пока сервер не установится и не запустится. Далее приведен make-файл, показывающий, как совмещаются программы:
all: server client
CC=cc
CFLAGS= -pedantic -Wall
# Для отладки удалите знак комментария в следующей строке
# DFLAGS=-DDEBUG_TRACE=1 -g
# Где и какую версию dbm мы применяем.
# Предполагается, что gdbm предустановлена в стандартном месте, но мы
# собираемся применять подпрограммы, совместимые с gdbrn, которые
# заставляют ее эмулировать ndbm. Делается это потому, что ndbm – 'самая
# стандартная' из версий dbm. Возможно, вам потребуется внести изменения
# в соответствии с вашим дистрибутивом.
DBM_INC_PATH=/usr/include/gdbm
DBM_LIB_PATH=/usr/lib
DBM_LIB_FILE=-lgdbm
# В некоторых дистрибутивах может понадобиться изменить предыдущую
# строку, чтобы включить библиотеку совместимости, как показано далее.
# DBM_LIB_FILE=-lgdbm_compat -lgdbm
.с.о:
$(CC) $(CFLAGS) -I$(DBM_INC_PATH) $(DFLAGS) -с $<
app_ui.o: app_ui.c cd_data.h
cd_dbm.o: cd_dbm.c cd_data.h
client_f.o: client_f.c cd_data.h cliserv.h
pipe_imp.o: pipe_imp.c cd_data.h cliserv.h
server.о: server.с cd_data.h cliserv.h
client: app_ui.o clientif.o pipe_imp.o
$(CC) -o client $(DFLAGS) app_ui.о clientif.o pipe_imp.o
server: server.о cd_dbm.o pipe_imp.o
$(CC) -o server -L$(DBM_LIB_PATH) $(DFLAGS) server.о cd_dbm.o pipe_imp.o -l$(DBM_LIB_FILE)
clean:
rm -f server client_app *.o *~
Наша задача – отделить часть приложения, работающую с базой данных, от пользовательского интерфейса приложения. Вам также необходимо выполнять один серверный процесс, но разрешить одновременное выполнение множества клиентских процессов и при этом сократить до минимума изменения, вносимые в существующий программный код. Везде, где это возможно, вы сохраните исходный текст приложения неизменным.
Для простоты у вас должна быть возможность создавать (и удалять) каналы внутри приложения, не заставляя администратора системы создавать именованные каналы перед тем, как вы сможете их применять.
Важно также не использовать состояние "активного ожидания", чтобы не тратить времени ЦП на ожидание события. Как вы видели, ОС Linux позволяет приостанавливать выполнение в ожидании событий без потребления значительных ресурсов. Следует применять блокирующие свойства каналов для гарантии эффективного использования ЦП. В конце концов, теоретически сервер может ждать в течение многих часов поступления запроса.
РеализацияВ предыдущей версии приложения, реализованного в виде единого процесса, с которой вы познакомились в главе 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.6
В клиент-серверную версию приложения вы хотите включить несколько именованных каналов и сопроводительный программный код для связи двух основных частей приложения. На рис. 13.7 показана необходимая структура.
Рис. 13.7
В данной реализации подпрограммы интерфейса и клиента, и сервера помещены в один файл pipe_imp.c. Это сохраняет в едином файле весь программный код, зависящий от применения именованных каналов в клиент-серверной реализации. Форматирование и упаковка передаваемых данных хранятся отдельно от подпрограмм, реализующих именованные каналы. В результате у вас появятся дополнительные файлы исходного текста программы, но с более логичным разделением. Структура вызовов в приложении показана на рис. 13.8.
Рис. 13.8
Файлы арр_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, сlient_if.c и pipe_imp.c.
Заголовочный файл cliserv.h
Сначала рассмотрим cliserv.h. Этот файл определяет клиент-серверный интерфейс. Он необходим и клиентской, и серверной программам.
1. Далее приведены необходимые директивы #include
.
#include
#include
#include
#include
#include
#include
#include
2. Затем вы определяете именованные каналы. Используйте один канал для сервера и по одному каналу для каждого клиента. Поскольку клиентов может быть несколько, клиентская программа включает идентификатор процесса в имя, таким образом, обеспечивая уникальность канала.
#define SERVER_PIPE «/tmp/server_pipe»
#define CLIENT_PIPE «/tmp/client_%d_pipe»
#define ERR_TEXT_LEN 80
3. Реализуйте команды как перечислимые типы, а не как директивы #define
.
Примечание
Это хорошая возможность для компилятора выполнить дополнительную проверку типов и помочь в отладке приложения, т.к. многие отладчики могут показывать имена перечислимых констант, но не имена, определенные директивой
#define
.
Первый оператор typedef
задает тип запроса, отправляемого на сервер; второй описывает тип серверного ответа клиенту.
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_fmd_cdc_entry
} client_request_e;
typedef enum {
r_success = 0,
r_failure,
r_find_no_more
} server_response_e;
4. Далее объявите структуру, которая будет формировать сообщение, передаваемое между двумя процессами в обоих направлениях.
Примечание
Поскольку на самом деле вам не нужно возвращать
cdc_entry
иcdt_entry
в одном ответе, вы могли бы сделать их объединением (union). Но для простоты можно оставить их отдельными элементами, кроме того, в этом случае легче поддерживать программный код.
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, и при необходимости будем обращаться к исходному программному коду.
Функции интерфейса клиентаРассмотрим файл clientif.c. Он предоставляет «поддельные» версии подпрограмм доступа к базе данных. Они кодируют запрос в структуре message_db_t
и затем применяют подпрограммы из файла pipe_imp.c для передачи запроса серверу. Такой подход позволит вам внести минимальные изменения в первоначальный файл app_ui.c.
Интерпретатор клиента
1. В этом файле реализовано девять функций для работы с базой данных, объявленных в файле cd_data.h. Делает он это передачей запросов серверу и затем возвратом ответа сервера из функции, действуя как посредник. Файл начинается с файлов #include
и констант.
#define _POSIX_SOURCE
#include
#include
#include
#include
#include
#include
#include
#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);
}
/* инициализация базы данных */
void database_close(void) {
client_ending();
}
4. Подпрограмма get_cdc_entry
вызывается для получения элемента каталога из базы данных по заданному названию компакт-диска в каталоге. В ней вы кодируете запрос в структуре message_db_t
и передаете его на сервер. Далее вы считываете обратно ответ в другую структуру типа message_db_t
. Если элемент найден, он включается в структуру message_db_t
как структура типа cdc_entry
, поэтому вы можете передать соответствующую часть структуры.
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] = ' ';
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 respondn»);
}
} else {
fprintf(stderr, «Server not accepting requestsn»);
}
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
и приводятся здесь для полноты картины. Сначала функция для извлечения дорожек компакт-диска.
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] = ' ';
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 respondn»);
}
} else {
fprintf(stderr, «Server not accepting requestsn»);
}
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 respondn»);
}
} else {
fprintf(stderr, «Server not accepting requestsn»);
}
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 respondn»);
}
} else {
fprintf(stderr, «Server not accepting requestsn»);
}
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 respondn»);
}
} else {
fprintf(stderr, «Server not accepting requestsn»);
}
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 respondn»);
}
} else {
fprintf(stderr, «Server not accepting requestsn»);
}
return(0);
}
Поиск в базе данных
Функция поиска по ключу компакт-диска сложнее. Пользователь этой функции рассчитывает вызвать ее один раз для начала поиска. Мы удовлетворили его ожидания в главе 7, задавая параметр *first_call_ptr
равным true
при первом вызове функции, и функция в этом случае возвращает первое найденное совпадение. При последующих вызовах функции поиска указатель *first_call_ptr
равен false
и возвращаются дальнейшие совпадения, по одному на каждый вызов.
Теперь, когда вы разделили приложение на два процесса, нельзя разрешать поиску обрабатывать по одному элементу на сервере, потому что другой клиент может запросить у сервера иной поиск, когда выполняется ваш поиск. Вы не можете заставить серверную часть хранить отдельно содержимое (как далеко продвинулся поиск) для поиска каждого клиента, т.к. клиент может просто остановить поиск на полпути, когда найден нужный компакт-диск или клиент "упал".
Можно либо изменить алгоритм поиска, либо, как показано в приведенном далее программном коде, спрятать сложность в подпрограмме интерфейса. Данный код вынуждает сервер возвращать все возможные совпадения с искомым значением и затем сохраняет их во временном файле до тех пор, пока клиент не запросит их.
1. Эта функция не так сложна, как кажется, просто в ней вызываются три функции канала send_mess_to_server
, start_resp_from_server
и read_resp_fromserver
, которые будут рассмотрены в следующем разделе.
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] = ' ';
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. Теперь приводится проверка условий с тремя уровнями вложенности, заставляющая вызывать функции из файла pipe_imp.c. Если сообщение успешно отправлено на сервер, клиент ждет ответа от сервера. Пока считывания с сервера успешны, совпадения с искомой величиной возвращаются в 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 respondingn»);
}
} else {
fprintf (stderr, «Server not accepting requestsn»);
}
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 {
/* не *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);
}