UNIX: разработка сетевых приложений
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 else18 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) возвращает ошибку, то наши функции проверяют, не совпадает ли код ошибки с EINTR (прерывание системного вызова сигналом, см. раздел 5.9). В этом случае прерванная функция вызывается повторно. Мы обрабатываем ошибку в этой функции, чтобы не заставлять процесс снова вызватьwriteилиread, поскольку целью наших функций является предотвращение обработки нехватки данных вызывающим процессом.writeВ разделе 14.3 мы покажем, что вызов функции
с флагомrecvпозволяет обойтись без использования отдельной функцииMSG_WAITALL.readnЗаметим, что наша функция
вызывает системную функциюreadlineодин раз для каждого байта данных. Это очень неэффективно, поэтому мы и написали в примечании «Ужасно медленно!». Возникает соблазн обратиться к стандартной библиотеке ввода-вывода (read). Об этом мы поговорим через некоторое время в разделе 14.8, но учтите, что это может привести к определенным проблемам. Буферизация, предоставляемаяstdio, решает проблемы с производительностью, но при этом создает множество логистических сложностей, которые в свою очередь порождают скрытые ошибки в приложении. Дело в том, что состояние буферовstdioнедоступно процессу. Рассмотрим, например, строчный протокол взаимодействия клиента и сервера, причем такой, что могут существовать разные независимые реализации клиентов и серверов (достаточно типичное явление; например, множество веб-браузеров и веб-серверов были разработаны независимо в соответствии со спецификацией HTTP). Хороший стиль программирования заключается в том, что эти программы должны не только ожидать от своих собеседников соблюдения того же протокола, но и контролировать трафик на возможность получения непредвиденного трафика. Подобные нарушения протокола должны рассматриваться как ошибки, чтобы программисты имели возможность находить и устранять неполадки в коде, а также обнаруживать попытки взлома систем. Обработка некорректного трафика должна давать приложению возможность продолжать работу. Буферизацияstdioмешает достижению перечисленных целей, поскольку приложение не может проверить наличие непредвиденных (некорректных) данных в буферахstdioв любой конкретный момент.stdioСуществует множество сетевых протоколов, основанных на использовании строк текста: SMTP, HTTP, FTP, finger. Поэтому соблазн работать со строками будет терзать вас достаточно часто. Наш совет: мыслить в терминах буферов, а не строк. Пишите код таким образом, чтобы считывать содержимое буфера, а не отдельные строки. Если же ожидается получение строки, ее всегда можно поискать в считанном буфере.
В листинге 3.12 приведена более быстрая версия функции
, использующая свой собственный буфер (а не буферизациюreadline). Основное достоинство этого буфера состоит в его открытости, благодаря чему вызывающий процесс всегда знает, какие именно данные уже приняты. Несмотря на это, использованиеstdioвсе равно может вызвать проблемы, как мы увидим в разделе 6.3. Системные функции типаreadlineничего не знают о внутреннем буфереselect, поэтому неаккуратно написанная программа с легкостью может очутиться в состоянии ожидания в вызовеreadline, при том, что данные уже будут находиться в буферахselect. По этой причине сочетание вызововreadlineиreadnне будет работать так, как этого хотелось бы, пока функцияreadlineне будет модифицирована с учетом наличия внутреннего буфера.readnЛистинг 3.12. Улучшенная версия функции readline
//lib/readline.c1 #include "unp.h"2 static int read_cnt;3 static char *read_ptr;4 static char read_buf[MAXLINE];5 static ssize_t6 my_read(int fd, char *ptr)7 {8 if (read_cnt <= 0) {9 again:10 if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {11 if (errno == EINTR)12 goto again;13 return(-1);14 } else if (read_cnt == 0)15 return(0);16 read_ptr = read_buf;17 }18 read_cnt--;19 *ptr = *read_ptr++;20 return(1);21 }22 ssize_t