Основы программирования в Linux - Нейл Мэтью
Шрифт:
Интервал:
Закладка:
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 в командной строке. Если да, она создаст новую базу данных. Если вызов подпрограммы database_initialize в файле cd_dbm.c завершится аварийно, будет выведено сообщение об ошибке. Если все хорошо и сервер работает, любые запросы от клиента направляются функции 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 failedn");
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 databasen");
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 pipen");
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; /* копирует команду обратно,
затем изменяет resp, как требовалось */
if (!start_resp_to_client(resp)) {
fprintf(stderr, "Server Warning:
start_resp_to_client %d failedn", resp.client_pid);
return;
}
resp.response = r_success;
memset(resp.error_text, ' ', 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 %dn", 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:nt%sn", strerror(save_errno));
if (!send_resp_to_client(resp)) {
fprintf(stderr,
"Server Warning:- failed to respond to %dn", resp.client_pid);
}
end_resp_to_client();
return;
}
Прежде чем рассматривать действующую реализацию канала, давайте обсудим последовательность событий, которые должны произойти для передачи данных между клиентским и серверным процессами. На рис. 13.9 показан запуск обоих, и клиентского, и серверного, процессов, а также то, как они образуют петлю во время обработки команд и ответов.
В этой реализации ситуация немного сложнее, т.к. в запросе на поиск клиент передает серверу одну команду и затем ждет один или несколько ответов от сервера. Это усложняет программу, особенно клиентскую часть.
Рис. 13.9
Канал
Далее показан файл реализации канала pipe_imp.с, в котором содержатся клиентские и серверные функции.
ПримечаниеКак вы видели в главе 10, может быть определено символическое имя DEBUG_TRACE для того, чтобы показать последовательность вызовов, в которых клиентский и серверный процессы передают друг другу сообщения.
Заголовочный файл для реализации канала1. Прежде всего, директивы #include:
#include "cd_data.h"
#include "cliserv.h"
2. Вы также определяете в файле программы несколько значений, нужных вам в разных функциях:
static int server_fd = -1;
static pid_t mypid = 0;
static char client_pipe_name[PATH_MAX + 1] = {' '};
static int client_fd = -1;
static int client_write_fd = -1;
Функции серверной стороныДалее нужно рассмотреть функции серверной стороны. В следующем разделе показаны функции, открывающие и закрывающие именованный канал и читающие сообщения от клиентов. В следующем за ним разделе приведен программный код, который открывает и закрывает клиентские каналы и пересылает по ним сообщения, основываясь на идентификаторе процесса, который клиент включает в свое сообщение.
Функции сервера1. Подпрограмма server_starting создает именованный канал, из которого сервер будет считывать команды. Далее она открывает канал для чтения. Этот вызов open будет блокировать выполнение, пока клиент не откроет канал для записи. Используйте режим блокировки для того, чтобы сервер мог выполнить блокировку вызовов read в канале в ожидании отправляемых ему команд.
int server_starting(void) {
#if DEBUG_TRACE
printf("%d server_starting()n", getpid());
#endif
unlink(SERVER_PIPE);
if (mkfifo(SERVER_PIPE, 0777) == -1) {
fprintf(stderr, "Server startup error, no FIFO createdn");
return(0);
}
if ((server_fd = open(SERVER_PIPE, O_RDONLY)) == -1) {
if (errno == EINTR) return(0);
fprintf(stderr, "Server startup error, no FIFO openedn");
return(0);
}
return(1);
}
2. Когда сервер завершает работу, он удаляет именованный канал, для того чтобы клиенты могли установить, что нет действующего сервера.
void server_ending(void) {
#if DEBUG_TRACE
printf("%d:- server_ending()n", getpid());
#endif
(void)close(server_fd);
(void)unlink(SERVER_PIPE);
}
3. Функция read_request_from_client будет блокировать чтение в серверном канале до тех пор, пока клиент не запишет в него сообщение.
int read_request_from_client(message_db_t *rec_ptr) {
int return_code = 0;
int read_bytes;
#if DEBUG_TRACE
printf("%d :- read_request_from_client()n", getpid());
#endif
if (server_fd != -1) {
read_bytes = read(server_fd, rec_ptr, sizeof(*rec_ptr));
...
}
return(return_code);
}
4. В особом случае, когда ни у одного клиента нет канала, открытого для записи, вызов read вернет 0, т.е. он обнаружит EOF (метку конца файла). Затем сервер закроет канал и откроет его снова так, что канал блокируется до тех пор, пока клиент также не откроет канал. Выполняется то же, что и при первом запуске сервера; вы инициализировали сервер повторно. Вставьте этот программный код в предыдущую функцию.
if (read_bytes == 0) {
(void)close(server_fd);
if ((server_fd = open(SERVER_PIPE, O_RDONLY)) == -1) {
if (errno != EINTR) {
fprintf(stderr, "Server error, FIFO open failedn");