UNIX: разработка сетевых приложений
37 fprintf(stderr, "%s: EOF on stdin\n", gf_time());38 stdineof = 1; /* с stdin все сделано */39 if (tooptr == toiptr)40 Shutdown(sockfd, SHUT_WR); /* отсылаем FIN */41 } else {42 fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(),43 n);44 toiptr += n; /* только что полученное из функции read число */45 FD_SET(sockfd, &wset); /* включаем бит в наборе чтения */46 }47 }48 if (FD_ISSET(sockfd, &rset)) {49 if ((n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {50 if (errno != EWOULDBLOCK)51 err_sys("read error on socket");52 } else if (n == 0) {53 fprintf(stderr, "%s: EOF on socket\n", gf_time());54 if (stdineof)55 return; /* нормальное завершение */56 else57 err_quit("str_cli: server terminated prematurely");58 } else {59 fprintf(stderr, "%s: read %d bytes from socket\n",60 gf_time(), n);61 friptr += n; /* только что полученное из функции read число */62 FD_SЕТ(STDOUT_FILЕNO, &wset); /* включаем бит в наборечтения */63 }64 }Чтение из стандартного потока ввода с помощью функции readЕсли стандартный поток ввода готов для чтения, мы вызываем функцию32-33. Третий ее аргумент — это количество свободного места в буфереread.toОбработка ошибкиЕсли происходит ошибка34-35, мы ничего не предпринимаем. Обычно эта ситуация — когда функцияEWOULDBLOCKсообщает нам о том, что дескриптор готов для чтения, а функция read возвращает ошибкуselect— не должна возникать, но тем не менее мы ее обрабатываем.EWOULDBLOCKВозвращение конца файла функцией readЕсли функция36-40возвращает нуль, мы закончили со стандартным потоком ввода. Флагreadустановлен. Если в буфере to больше нет данных для отправки (stdineofравноtooptr), функцияtoiptrотправляет серверу сегмент FIN. Если в буфереshutdownеще есть данные для отправки, сегмент FIN не может быть отправлен до тех пор, пока содержимое буфера не будет записано в сокет.toПРИМЕЧАНИЕМы выводим в стандартный поток сообщений об ошибках строку, отмечающую конец файла, вместе с текущим временем. Мы покажем, как мы используем этот вывод, после описания функции. Аналогичные вызовы функции fprintf выполняются неоднократно в процессе выполнения нашей функции.
Возвращение данных функцией readКогда функция read возвращает данные, мы увеличиваем на единицу41-45. Мы также включаем бит, соответствующий сокету, в наборе флагов записи, чтобы позже при проверке этого бита в цикле он был включен и тем самым инициировалась бы попытка записи в сокет с помощью функцииtoiptr.writeПРИМЕЧАНИЕЭто одно из непростых конструктивных решений, которые приходится принимать при написании кода. У нас есть несколько альтернатив. Вместо установки бита в наборе записи мы можем ничего не делать, и в этом случае функция select будет проверять возможность записи в сокет, когда она будет вызвана в следующий раз. Но это требует дополнительного прохода цикла и вызова функции select, когда мы уже знаем, что у нас есть данные для записи в сокет. Другой вариант — дублировать код, который записывает в сокет, но это кажется расточительным, к тому же это возможный источник ошибки (в случае, если в этой части дублируемого кода есть ошибка и мы обнаруживаем и устраняем ее только в одном месте). Наконец, мы можем создать функцию, записывающую в сокет, и вызывать эту функцию вместо дублирования кода, но эта функция должна использовать три локальные переменные совместно с функцией str_cli, что может привести к необходимости сделать эти переменные глобальными. Выбор, сделанный в нашем случае, — это результат субъективного мнения автора относительно того, какой из описанных трех вариантов предпочтительнее.
Чтение из сокета с помощью функции readЭти строки кода аналогичны выражению48-64, только что описанному для случая, когда стандартный поток ввода готов для чтения. Если функцияifвозвращает ошибкуread, ничего не происходит. Если мы встречаем признак конца файла, присланный сервером, это нормально, когда мы уже получили признак конца файла в стандартном потоке ввода. Но иначе это будет ошибкой, означающей преждевременное завершение работы сервера (EWOULDBLOCK). Если функцияServer terminated prematurelyвозвращает некоторые данные,readувеличивается на единицу и в наборе флагов записи включается бит для стандартного потока вывода, с тем чтобы попытаться записать туда данные в следующей части функции.friptrВ листинге 16.3 показана последняя часть нашей функции.
Листинг 16.3. Функция str_cli: третья часть, запись в стандартный поток вывода или сокет
//nonblock/strclinonb.c65 if (FD_ISSET(STDOUT_FILENO, &wset) && ((n = friptr - froptr) > 0)) {66 if ((nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {67 if (errno != EWOULDBLOCK)68 err_sys("write error to stdout");