вызывается для блокируемого сокета и новое соединение недоступно, процесс переводится в состояние ожидания.
Если функция
accept
вызывается для неблокируемого сокета и новое соединение недоступно, возвращается ошибка
EWOULDBLOCK
.
4. Инициирование исходящих соединений: функция
connect
для TCP. (Вспомните, что функция connect может использоваться с UDP, но она не вызывает создания «реального» соединения — она лишь заставляет ядро сохранить IP-адрес и номер порта собеседника.) В разделе 2.5 мы показали, что установление соединения TCP включает трехэтапное рукопожатие и что функция connect не возвращает
управление, пока клиент не получит сегмент ACK или SYN. Это значит, что функция TCP
connect
всегда блокирует вызывающий процесс как минимум на время обращения (RTT) к серверу.
Если функция
connect
вызывается для неблокируемого сокета TCP и соединение не может быть установлено немедленно, инициируется установление соединения (например, отправляется первый пакет трехэтапного рукопожатия TCP), но возвращается ошибка
EINPROGRESS
. Обратите внимание, что эта ошибка отличается от ошибки, возвращаемой в первых трех сценариях. Также отметим, что некоторые соединения могут быть установлены немедленно, когда сервер находится на том же узле, что и клиент, поэтому даже в случае неблокируемого вызова функции
connect
мы должны быть готовы к тому, что она успешно выполнится. Пример неблокируемой функции
connect
мы покажем в разделе 16.3.
ПРИМЕЧАНИЕ
Традиционно System V возвращала для неблокируемой операции ввода-вывода, которую невозможно выполнить, ошибку EAGAIN, в то время как Беркли-реализации возвращали ошибку EWOULDBLOCK. Еще больше дело запутывается тем, что согласно POSIX.1 используется EAGAIN, в то время как в POSIX.1g определено, что используется EWOULDBLOCK. К счастью, большинство систем (включая SVR4 и 4.4BSD) определяют один и тот же код для этих двух ошибок (проверьте свой системный заголовочный файл <sys/errno.h>), поэтому не важно, какой из них использовать. В нашем тексте мы используем ошибку EWOULDBLOCK, как определяется в POSIX.
В разделе 6.2 мы представили различные модели ввода-вывода и сравнили неблокируемый ввод-вывод с другими моделями. В этой главе мы покажем примеры четырех типов операций и разработаем новый тип клиента, аналогичный веб-клиенту, инициирующий одновременно множество соединений TCP при помощи неблокируемой функции
connect
.
16.2. Неблокируемые чтение и запись: функция str_cli (продолжение)
Мы снова возвращаемся к нашей функции
str_cli
, которую мы обсуждали в разделах 5.5 и 6.4. Последняя ее версия, задействующая функцию
select
, продолжает использовать блокируемый ввод-вывод. Например, если в стандартном устройстве ввода имеется некоторая строка, мы читаем ее с помощью функции
fgets
и затем отправляем серверу с помощью функции
writen
. Но вызов функции
writen
может вызвать блокирование процесса, если буфер отправки сокета полон. В то время как мы заблокированы в вызове функции
writen
, данные могут быть доступны для чтения из приемного буфера сокета. Аналогично, когда строка ввода доступна из сокета, мы можем заблокироваться в последующем вызове функции
fputs
, если стандартный поток вывода работает медленнее, чем сеть. Наша цель в данном разделе — создать версию этой функции, использующую неблокируемый ввод-вывод. Блокирование будет предотвращено, благодаря чему в это время мы сможем сделать еще что-то полезное.
К сожалению, добавление неблокируемого ввода-вывода значительно усложняет управление буфером функции, поэтому мы будем представлять функцию частями. Мы также заменим вызовы функций из стандартной библиотеки ввода-вывода на обычные
read
и
write
. Это даст возможность отказаться от функций стандартной библиотеки ввода-вывода с неблокируемыми дескрипторами, так как их применение может привести к катастрофическим последствиям.
Мы работаем с двумя буферами: буфер to содержит данные,
направляющиеся из стандартного потока ввода к серверу, а буфер
fr
— данные, приходящие от сервера в стандартный поток вывода. На рис. 16.1 представлена организация буфера
to
и указателей в буфере.
Рис. 16.1. Буфер, содержащий данные из стандартного потока ввода, идущие к сокету
Указатель
toiptr
указывает на следующий байт, в который данные могут быть считаны из стандартного потока ввода. Указатель
tooptr
указывает на следующий байт, который должен быть записан в сокет. Число байтов, которое может быть считано из стандартного потока ввода, равно
&to[MAXLINE]
минус
toiptr
. Как только значение
tooptr
достигает
toiptr
, оба указателя переустанавливаются на начало буфера.
На рис. 16.2 показана соответствующая организация буфера
fr
. В листинге 16.1 [1] представлена первая часть функции.
Рис. 16.2. Буфер, содержащий данные из сокета, идущие к стандартному устройству вывода
Листинг 16.1. Функция str_cli: первая часть, инициализация и вызов функции
//nonblock/strclinonb.c
1 #include "unp.h"
1
Все исходные коды программ, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com.
2 void
3 str_cli(FILE *fp, int sockfd)
4 {
5 int maxfdp1, val, stdineof;
6 ssize_t n, nwritten;
7 fd_set rset, wset;
8 char to[MAXLINE], fr[MAXLINE];
9 char *toiptr, *tooptr, *friptr, *froptr;
10 val = Fcntl(sockfd, F_GETFL, 0);
11 Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
12 val = Fcntl(STDIN_FILENO, F_SETFL, 0);
13 Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
14 val = Fcntl(STDOUT_FILENO, F_SETFL, 0);
15 Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);