Между тем, после того как клиентский процесс открыл серверный канал 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);