Как и в серверных функциях, клиент получает назад результаты от сервера с помощью трех функций, обслуживающих множественные результаты поисков.
1. Данная клиентская функция запускается для ожидания ответа сервера. Она открывает канал клиента только для чтения и затем повторно открывает файл канала только для записи. Чуть позже в этом разделе вы поймете почему.
int start_resp_from_server(void) {
#if DEBUG_TRACE
printf("%d :- start_resp_from_server\n", getpid);
#endif
if (client_pipe_name[0] == '\0') return(0);
if (client_fd != -1) return(1);
client_fd = open(client_pipe_name, O_RDONLY);
if (client_fd != -1) {
client_write_fd = open(client_pipe_name, O_WRONLY);
if (client_write_fd != -1) return(1);
(void)close(client_fd);
client_fd = -1;
}
return(0);
}
2. Далее приведена основная операция read
, которая получает с сервера совпадающие элементы базы данных.
int read_resp_from_server(message_db_t *rec_ptr) {
int read_bytes;
int return_code = 0;
#if DEBUG_TRACE
printf("%d :- reader_resp_from_server\n", getpid);
#endif
if (!rec_ptr) return(0);
if (client_fd = -1) return(0);
read_bytes = read(client_fd, rec_ptr, sizeof(*rec_ptr));
if (read_bytes = sizeof(*rec_ptr)) return_code = 1;
return(return_code);
}
3. И в заключение приведена клиентская функция, помечающая конец ответа сервера.
void end_resp_from_server(void) {
#if DEBUG_TRACE
printf("%d :- end_resp_from_server\n", getpid);
#endif
/* В реализации канала эта функция пустая */
}
Второй дополнительный вызов open
канала клиента для записи в start_resp_from_server
client_write_fd = open(client_pipe_name, O_WRONLY);
применяется для защиты от ситуации гонок, когда серверу необходимо быстро откликаться на несколько запросов клиента,
Для того чтобы стало понятнее, рассмотрим такую последовательность событий:
1. Клиент пишет запрос к серверу.
2. Сервер читает запрос, открывает канал клиента и отправляет обратно ответ, но приостанавливает выполнение до того, как успеет закрыть канал клиента.
3. Клиент открывает канал для чтения, читает первый ответ и закрывает свой канал.
4. Далее клиент посылает новую команду и открывает клиентский канал для чтения.
5. Сервер возобновляет работу, закрывая свой конец клиентского канала.
К сожалению, в этот момент клиент пытается считать из канала ответ на свой следующий запрос, но read
вернет 0 байтов, поскольку ни один процесс не открыл клиентский канал для записи.
Разрешив клиенту открыть канал как для чтения, так и для записи, и устранив тем самым необходимость повторного открытия канала, вы избежите подобной ситуации гонок. Учтите, что клиент никогда не пишет в канал, поэтому нет опасности считывания ошибочных данных.
Резюме, касающееся приложения
Вы разделили приложение, управляющее базой данных компакт-дисков, на клиентскую и серверную части, что позволило разрабатывать независимо пользовательский интерфейс и внутреннюю технологию работы с базой данных. Как видите, четко определенный интерфейс базы данных дает возможность каждому важному элементу приложения наилучшим образом использовать машинные ресурсы. Если пойти чуть дальше, можно было бы заменить реализацию с помощью каналов на сетевой вариант и применить выделенный компьютер для сервера базы данных. В
Резюме
В этой главе вы рассмотрели передачу данных между процессами с помощью каналов. Сначала вы познакомились с неименованными каналами, которые создаются вызовом popen
или pipe
, и посмотрели, как, применяя канал и вызов dup
, можно передать данные из одной программы в стандартный ввод другой. Далее вы перешли к именованным каналам и узнали, как можно передавать данные между несвязанными программами. В заключение вы реализовали простой пример клиент- серверного приложения, используя каналы FIFO для обеспечения не только синхронизации процессов, но и организации двунаправленного потока данных.
Глава 14
Семафоры, совместно используемая память и очереди сообщений