Книга: UNIX: разработка сетевых приложений

Пример: передача двоичных структур между клиентом и сервером

Пример: передача двоичных структур между клиентом и сервером

Теперь мы изменим код клиента и сервера, чтобы передавать через сокет не текстовые строки, а двоичные значения. Мы увидим, что клиент и сервер работают некорректно, когда они запущены на узлах с различным порядком байтов или на узлах с разными размерами целого типа long (см. табл. 1.5).

Функции main наших клиента и сервера не изменяются. Мы определяем одну структуру для двух аргументов, другую структуру для результата и помещаем оба определения в наш заголовочный файл sum.h, представленный в листинге 5.12. В листинге 5.13 показана функция str_cli.

Листинг 5.12. Заголовочный файл sum.h

//tcpcliserv/sum.h
1 struct args {
2  long arg1;
3  long arg2;
4 };
5 struct result {
6  long sum;
7 };

Листинг 5.13. Функция str_cli, отправляющая два двоичных целых числа серверу

//tcpcliserv/str_cli09.c
 1 #include "unp.h"
 2 #include "sum.h"
 3 void
 4 str_cli(FILE *fp, int sockfd)
 5 {
 6  char sendline[MAXLINE];
 7  struct args args;
 8  struct result result;
 9  while (Fgets(sendline, MAXLINE, fp) != NULL) {
10   if (sscanf(sendline, "%ld%ld", &args.arg1, &args.arg2) != 2) {
11    printf("invalid input, %s", sendline);
12    continue;
13   }
14   Writen(sockfd, &args, sizeof(args));
15   if (Readn(sockfd, &result, sizeof(result)) == 0)
16    err_quit("str_cli: server terminated prematurely");
17   printf("%ldn", result.sum);
18  }
19 }
10-14
 Функция sscanf преобразует два аргумента из текстовых строк в двоичные. Мы вызываем функцию writen для отправки структуры серверу.

15-17 Мы вызываем функцию readn для чтения ответа и выводим результат с помощью функции printf.

В листинге 5.14 показана наша функция str_echo.

Листинг 5.14. Функция str_echo, складывающая два двоичных целых числа

//tcpcliserv/str_echo09.c
 1 #include "unp.h"
 2 #include "sum.h"
 3 void
 4 str_echo(int sockfd)
 5 {
 6  ssize_t n;
 7  struct args args;
 8  struct result result;
 9  for (;;) {
10   if ((n = Readn(sockfd, &args, sizeof(args))) == 0)
11    return; /* соединение закрыто удаленным концом */
12   result.sum = args.arg1 + args.arg2;
13   Writen(sockfd, &result, sizeof(result));
14  }
15 }
9-14
 Мы считываем аргументы при помощи вызова функции readn, вычисляем и запоминаем сумму и вызываем функцию writen для отправки результирующей структуры обратно.

Если мы запустим клиент и сервер на двух машинах с аналогичной архитектурой, например на двух компьютерах SPARC, все будет работать нормально:

solaris % tcpcli09 12.106.32.254
11 22 мы вводим эти числа
33    а это ответ сервера
-11 -44
-55

Но если клиент и сервер работают на машинах с разными архитектурами, например, сервер в системе FreeBSD на SPARC, в которой используется обратный порядок байтов (big-endian), а клиент — в системе Linux на Intel с прямым порядком байтов (little-endian), результат будет неверным:

linux % tcpcli09 206.168.112.96
1 2       мы вводим эти числа
3         и сервер дает правильный ответ
-22 -77   потом мы вводим эти числа
-16777314 и сервер дает неверный ответ

Проблема заключается в том, что два двоичных числа передаются клиентом через сокет в формате с прямым порядком байтов, а сервер интерпретирует их как целые числа, записанные с обратным порядком байтов. Мы видим, что это допустимо для положительных целых чисел, но для отрицательных такой подход не срабатывает (см. упражнение 5.8). Действительно, в подобной ситуации могут возникнуть три проблемы:

1. Различные реализации хранят двоичные числа в различных форматах. Наиболее характерный пример — прямой и обратный порядок байтов, описанный в разделе 3.4.

2. Различные реализации могут хранить один и тот же тип данных языка С по- разному. Например, большинство 32-разрядных систем Unix используют 32 бита для типа long, но 64-разрядные системы обычно используют 64 бита для того же типа данных (см. табл. 1.5). Нет никакой гарантии, что типы short, int или long имеют какой-либо определенный размер.

3. Различные реализации по-разному упаковывают структуры в зависимости от числа битов, используемых для различных типов данных, и ограничений по выравниванию для данного компьютера. Следовательно, неразумно передавать через сокет двоичные структуры.

Есть два общих решения проблемы, связанной с различными форматами данных:

1. Передавайте все численные данные как текстовые строки. Это то, что мы делали в листинге 5.11. При этом предполагается, что у обоих узлов один и тот же набор символов.

2. Явно определяйте двоичные форматы поддерживаемых типов данных (число битов и порядок байтов) и передавайте все данные между клиентом и сервером в этом формате. Пакеты удаленного вызова процедур (Remote Procedure Call, RPC) обычно используют именно эту технологию. В RFC 1832 [109] описывается стандарт представления внешних данных (External Data Representation, XDR), используемый с пакетом Sun RPC.

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


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