При написании сетевых программ следует помнить: в каждой компьютерной архитектуре принято по-своему представлять некоторые типы данных. Мы уже отмечали, что целочисленные значения могут храниться с разным порядком следования байтов. Но существуют и другие особенности. Например, тип данных long в языке C может занимать как 32, так и 64 бита в зависимости от системы. В случае со структурами все только усложняется, ведь платформы задействуют различные правила выравнивания полей структуры по границам адреса, из-за чего величина сдвига между полями может варьироваться.
Из-за указанных отличий в представлении данных приложения, передающие информацию по сети между двумя гетерогенными (несовместимыми) системами, должны использовать некое общее соглашение относительно кодирования и декодирования этой информации. Процесс приведения данных к стандартному формату, подходящему для передачи по сети, называется
Однако вместо маршалинга часто используется более простая методика: все передаваемые данные переводятся в текстовый вид, а отдельные элементы разделяются специальными символами (обычно символом новой строки). Одним из преимуществ такого подхода является то, что для отладки приложения можно задействовать программу telnet. Для этого достаточно следующей команды:
$ telnet host port
Затем можно вводить строчки текста, которые будут посланы приложению, и просматривать полученные ответы. Данная методика будет продемонстрирована в разделе 55.11.
Проблема, связанная с различиями в представлении информации в гетерогенных системах, касается не только сетевого взаимодействия, но и любых механизмов обмена данными между системами. Например, те же проблемы существуют при передаче файлов, хранящихся на диске или магнитной ленте. Просто сетевое программирование на сегодняшний день является наиболее распространенным контекстом, в котором можно столкнуться с подобной проблемой.
Для работы с данными, передаваемыми с помощью потокового сокета и закодированными в виде разбитого на отдельные строки текста, удобно использовать функцию readLine(), представленную в листинге 55.1.
#include "read_line.h"
ssize_t readLine(int
Возвращает либо количество байтов, скопированных в buffer (не считая завершающего нулевого символа), либо 0, если обнаружен конец файла, либо -1 при ошибке
Функция readLine() считывает байты из файла, указанного дескриптором fd, пока не обнаруживает символ новой строки. Входящая последовательность байтов сохраняется в участке памяти, на который ссылается аргумент buffer; размер этого участка должен быть не меньше n байт. Возвращаемая строка всегда содержит в конце нулевой символ; таким образом, объем полученных данных не превышает (n — 1) байт. В случае успеха функция readLine() возвращает количество байтов, помещенных в buffer, не считая конечного нулевого символа.
Листинг 55.1. Построчное чтение данных
sockets/read_line.c
#include
#include
#include "read_line.h" /* Объявление readLine() */
ssize_t
readLine(int fd, void *buffer, size_t n)
{
ssize_t numRead; /* Сколько байтов было прочитано */
size_t totRead; /* Общее количество прочитанных байтов на этот момент */
char *buf;
char ch;
if (n <= 0 || buffer == NULL) {
errno = EINVAL;
return -1;
}
buf = buffer; /* Арифметика указателей не поддерживается для "void *" */
totRead = 0;
for (;;) {
numRead = read(fd, &ch, 1);
if (numRead == -1) {
if (errno == EINTR) /* Прерывание — > перезапускаем read() */
continue;
else
return -1; /* Какая-то другая ошибка */
} else if (numRead == 0) { /* Конец файла */
if (totRead == 0) /* Ничего не прочитано; возвращаем 0 */
return 0;
else
/* Прочитано какое-то количество байтов; добавляем '\0' */
break;
} else { /* На данном этапе 'numRead' должно быть равно 1 */
if (totRead < n — 1) { /* Отклоняем лишние байты: > (n — 1) */
totRead++;
*buf++ = ch;
}
if (ch == '\n')
break;
}
}
*buf = '\0';
return totRead;
}
sockets/read_line.c