Книга: Основы программирования в Linux

Канал

Канал

Далее показан файл реализации канала 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");
  }
  return(0);
 }
 read_bytes = read(server_fd, rec_ptr, sizeof(*rec_ptr));
}
if (read_bytes == sizeof(*rec_ptr)) return_code = 1;

Сервер — это единственный процесс, способный одновременно обслуживать множество клиентов. Поскольку каждый клиент применяет свой канал для получения ответов, адресованных ему, сервер, для того чтобы отправить ответы разным клиентам, должен писать в разные каналы. Поскольку файловые дескрипторы — это ограниченный ресурс, сервер открывает клиентский канал для записи только тогда, когда у него есть данные для отправки.

В программном коде открытие клиентского канала, запись в него и закрытие канала разделены на три отдельные функции. Когда вы возвращаете многочисленные результаты поиска, такой подход необходим, для того чтобы можно было открыть канал один раз, записать в него множество ответов и затем снова закрыть канал.

Прокладка каналов

1. Сначала откройте канал клиента.

int start_resp_to_client(const message_db_t mess_to_send) {
#if DEBUG_TRACE
 printf("%d :- start_resp_to_client()n", getpid());
#endif
 (void)sprintf(client_pipe_name, CLIENT_PIPE,
  mess_to_send.client_pid);
 if ((client_fd = open(client_pipe_name, O_WRONLY)) == -1) return(0);
 return(1);
}

2. Все сообщения отправляются с помощью данной функции. Соответствующие клиентские функции, которые принимают сообщение, вы увидите позже.

int send_resp_to_client(const message_db_t mess_to_send) {
 int write_bytes;
#if DEBUG_TRACE
 printf("%d :- send_resp_to_client()n", getpid());
#endif
 if (client_fd == -1) return(0);
 write_bytes = write(client_fd, &mess_to_send, sizeof(mess_to_send));
 if (write_bytes != sizeof(mess_to_send)) return(0);
 return(1);
}

3. В заключение закройте клиентский канал.

void end resp_to_client(void) {
#if DEBUG_TFACE
 printf("%d :- end_resp_to_client()n", getpid());
#endif
 if (client_fd != -1) {
  (void)close(сlient_fd);
  client_fd = -1;
 }
}

Функции на стороне клиента

Дополнение к серверу — клиентские функции в файле pipe_imp.c. Они очень похожи на серверные функции за исключением функции с интригующим именем send_mess_to_server.

Клиентские функции

1. После проверки доступности сервера функция client_starting инициализирует канал клиентской стороны.

int client_starting(void) {
#if DEBUG_TFACE
 printf("%d client_startingn", getpid());
#endif
 mypid = getpid();
 if ((server_fd = open(SERVER_PIPE, O_WRONLY)) == -1) {
  fprintf(stderr, "Server not runningn");
  return(0);
 }
 (void)sprintf(client pipe name, CLIENT_PIPE, mypid);
 (void)unlink(client_pipe_name);
 if (mkfifo(client_pipe_name, 0777) == -1) {
  fprintf(stderr, "Unable to create client pipe %sn", client_pipe_name);
  return(0);
 }
 return(1);
}

2. Функция client_ending закрывает файловые дескрипторы и удаляет ненужный теперь именованный канал.

void client_ending(void) {
#if DEBUG_TRACE
 printf("%d client_ending()n", getpid());
#endif
 if (client_write_fd != -1) (void)close(client_write_fd);
 if (client_fd != -1) (void)close(client_fd);
 if (server_fd != -1) (void)close(server_fd);
 (void)unlink(client_pipe_name);
}

3. Функция send_mess_to_server передает запрос через канал сервера.

int send_mess_to_server(message_db_t mess_to_send) {
 int write_bytes;
#if DEBUG_TRACE
 printf("%d send_mess_to_server()n", getpid());
#endif
 if (server_fd == -1) return(0);
 mess_to_send.client_pid = mypid;
 write_bytes = write(server_fd, &mess_to_send, sizeof(mess_to_send));
 if (write_bytes != sizeof(mess_to_send)) return(0);
 return(1);
}

Как и в серверных функциях, клиент получает назад результаты от сервера с помощью трех функций, обслуживающих множественные результаты поисков.

Получение результатов с сервера

1. Данная клиентская функция запускается для ожидания ответа сервера. Она открывает канал клиента только для чтения и затем повторно открывает файл канала только для записи. Чуть позже в этом разделе вы поймете почему.

int start_resp_from_server(void) {
#if DEBUG_TRACE
 printf("%d :- start_resp_from_server()n", getpid());
#endif
 if (client_pipe_name[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 байтов, поскольку ни один процесс не открыл клиентский канал для записи.

Разрешив клиенту открыть канал как для чтения, так и для записи, и устранив тем самым необходимость повторного открытия канала, вы избежите подобной ситуации гонок. Учтите, что клиент никогда не пишет в канал, поэтому нет опасности считывания ошибочных данных.

Оглавление книги


Генерация: 0.077. Запросов К БД/Cache: 2 / 0
поделиться
Вверх Вниз