повторил вызов функции readили writeдля ввода или вывода оставшихся байтов. (Некоторые версии Unix ведут себя аналогично при записи в канал (pipe) более 4096 байт.) Этот сценарий всегда возможен на потоковом сокете при выполнении функции read, но с функцией writeон обычно наблюдается, только если сокет неблокируемый. Тем не менее вместо writeмы всегда вызываем функцию writenна тот случай, если в данной реализации возможно возвращение меньшего количества данных, чем мы запрашиваем.
Введем три функции для чтения и записи в потоковый сокет.
#include "unp.h"
ssize_t readn(int filedes , void * buff , size_t nbytes );
ssize_t writen(int filedes , const void * buff , size_t nbytes );
ssize_t readline(int filedes , void * buff , size_t maxlen );
Все функции возвращают: количество считанных или записанных байтов, -1 в случае ошибки
В листинге 3.9 представлена функция readn, в листинге 3.10 функция writen, а в листинге 3.11 функция readline.
Листинг 3.9. Функция readn: считывание n байт из дескриптора
//lib/readn.c
1 #include "unp.h"
2 ssize_t /* Считывает n байт из дескриптора */
3 readn(int fd, void *vptr, size_t n)
4 {
5 size_t nleft;
6 ssize_t nread;
7 char *ptr;
8 ptr = vptr;
9 nleft = n;
10 while (nleft > 0) {
11 if ((nread = read(fd, ptr, nleft)) < 0) {
12 if (errno == EINTR)
13 nread = 0; /* и вызывает снова функцию read() */
14 else
15 return (-1);
16 } else if (nread == 0)
17 break; /* EOF */
18 nleft -= nread;
19 ptr += nread;
20 }
21 return (n - nleft); /* возвращает значение >= 0 */
22 }
Листинг 3.10. Функция writen: запись n байт в дескриптор
//lib/writen.c
1 #include "unp.h"
2 ssize_t /* Записывает n байт в дескриптор */
3 writen(int fd, const void *vptr, size_t n)
4 {
5 size_t nleft;
6 ssize_t nwritten;
7 const char *ptr;
8 ptr = vptr;
9 nleft = n;
10 while (nleft > 0) {
11 if ((nwritten = write(fd, ptr, nleft)) <= 0) {
12 if (errno == EINTR)
13 nwritten = 0; /* и снова вызывает функцию write() */
14 else
15 return (-1); /* ошибка */
16 }
17 nleft -= nwritten;
18 ptr += nwritten;
19 }
20 return (n);
21 }
Листинг 3.11. Функция readline: считывание следующей строки из дескриптора, по одному байту за один раз
//test/readline1.с
1 #include "unp.h"
/* Ужасно медленная версия, приводится только для примера */
2 ssize_t
3 readline(int fd, void *vptr, size_t maxlen)
4 {
5 ssize_t n, rc;
6 char c, *ptr;
7 ptr = vptr;
8 for (n = 1; n < maxlen; n++) {
9 again:
10 if ((rc = read(fd, &c, 1)) == 1) {
11 *ptr++ = c;
12 if (c == '\n')
13 break; /* записан символ новой строки, как в fgets() */
14 } else if (rc == 0) {
15 if (n == 1)
16 return (0); /* EOF, данные не считаны */
17 else
18 break; /* EOF, некоторые данные были считаны */
19 } else {
20 if (errno == EINTR)
21 goto again;
22 return (-1); /* ошибка, errno задается функцией read() */
23 }
24 }
25 *ptr = 0; /* завершаем нулем, как в fgets() */
26 return (n);
27 }
Если функция чтения или записи ( readили write) возвращает ошибку, то наши функции проверяют, не совпадает ли код ошибки с EINTR (прерывание системного вызова сигналом, см. раздел 5.9). В этом случае прерванная функция вызывается повторно. Мы обрабатываем ошибку в этой функции, чтобы не заставлять процесс снова вызвать readили write, поскольку целью наших функций является предотвращение обработки нехватки данных вызывающим процессом.
В разделе 14.3 мы покажем, что вызов функции recvс флагом MSG_WAITALLпозволяет обойтись без использования отдельной функции readn.
Заметим, что наша функция readlineвызывает системную функцию
readодин раз для каждого байта данных. Это очень неэффективно, поэтому мы и написали в примечании «Ужасно медленно!». Возникает соблазн обратиться к стандартной библиотеке ввода-вывода ( stdio). Об этом мы поговорим через некоторое время в разделе 14.8, но учтите, что это может привести к определенным проблемам. Буферизация, предоставляемая stdio, решает проблемы с производительностью, но при этом создает множество логистических сложностей, которые в свою очередь порождают скрытые ошибки в приложении. Дело в том, что состояние буферов stdioнедоступно процессу. Рассмотрим, например, строчный протокол взаимодействия клиента и сервера, причем такой, что могут существовать разные независимые реализации клиентов и серверов (достаточно типичное явление; например, множество веб-браузеров и веб-серверов были разработаны независимо в соответствии со спецификацией HTTP). Хороший стиль программирования заключается в том, что эти программы должны не только ожидать от своих собеседников соблюдения того же протокола, но и контролировать трафик на возможность получения непредвиденного трафика. Подобные нарушения протокола должны рассматриваться как ошибки, чтобы программисты имели возможность находить и устранять неполадки в коде, а также обнаруживать попытки взлома систем. Обработка некорректного трафика должна давать приложению возможность продолжать работу. Буферизация stdioмешает достижению перечисленных целей, поскольку приложение не может проверить наличие непредвиденных (некорректных) данных в буферах stdioв любой конкретный момент.