Неблокирующий connect()
В продолжение темы о замене блокирующего вызова gethostbyname(),
хочется рассказать о другой функции интерфейса сокетов, connect().
Она имеет следующий прототип:
int
connect(int s, const struct sockaddr *name, socklen_t namelen);
Эта функция предназначена для установки соединения с удаленной машиной после того,
как на локальной машине уже открыт сокет под это соединение.
Функция забавна уже тем, как в нее передаются параметры. Все дело
в том, что она может использоваться с любыми протоколами
передачи данных (а не только TCP/IP), поэтому второй параметр
имеет странный тип "указатель на struct sockaddr",
которая на самом деле содержит в себе лишь общие определения
для всех протоколов. Пусть вас не смущает название этого параметра:
name обозначает "имя" не в смысле имени DNS, а в смысле
уникального идентификатора сокета (например парой ip-адрес и номер порта).
Я не буду приводить примеры того, как эта функция используется,
скорее всего вы и так все это можете найти в документации на вашу
операционную систему. Дальше мне бы хотелось немного поговорить
о том, как эта функция будет работать.
Проблема связанная с ней, опять заключается в том, что
установка соединения вполне вероятно может быть очень
длительной операцией за счет плохих каналов передачи данных.
Ведь этот процесс заключается в том, что по сети передаются
несколько пакетов "туда и обратно" с просьбой открыть сокет,
послать подтверждение и прочее... то есть, фактически,
connect() опять же ничем принципиально не отличается
от функций чтения и записи данных в сокеты. Тем не менее, за счет
использования select() мы можем читать из сокета и писать
в сокет по готовности, а окончания connect() вынуждены ждать...
Вообще говоря, операции ввода-вывода могут быть блокирующими, неблокирующими,
управляемыми сигналами, асинхронными... Нас интересуют первые два типа
подобных операций, как видно из названия:
-
Блокирующие операции ввода-вывода приостанавливают процесс до окончания выполнения
операции. Например, если что-то читается из сокета (вызовом
read()),
то управление вернется в программу только в том случае, если пришли
реальные данные по этому соединению, либо произошла ошибка (например, разрыв
связи).
-
Неблокирующие операции возвращают управление в процесс сразу же
после вызова, завершая операцию параллельно с выполнением процесса.
Реальные сетевые программы практически никогда не используют блокирующие
функции: это было бы слишком медленно. Или, точнее, могут использовать
блокирующий read(), но только удостоверившись в том, что
ему есть что прочитать посредством предварительного вызова select().
Но connect() все равно остается блокирующим! До тех пор, пока
не будет установлена соответствующая опция сокета. То, о чем я хочу
рассказать чуть ниже, не является фокусом, этот прием после Netscape очень давно применяется
повсеместно, я сам вычитал это из Unix Network Programming, но почему-то
очень часто этого не делают.
Все операции с сокетами можно сделать неблокирующими посредством
предварительного вызова функции fcntl():
int fd = socket(AF_INET, SOCK_STREAM, 0);
int flags = fcntl(fd, F_GETFL, 0);
if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
{
/*
* Не получилось...
*/
}
"Неблокированность" сокета устанавливается вызовом fcntl()
с параметром F_SETFL и соответствующим набором флагов, в который
включен O_NONBLOCK. После этого можно не опасаясь вызывать
connect(), вызывающий процесс продолжит свое выполнение
в любом случае, даже если на установку соединения потребуется несколько
десятков секунд:
if(connect(fd, (sockaddr*)&servaddr, sizeof(servaddr)) != 0)
{
|
| |
| Лирическое отступление: |
 |
|
| |
|
Проверка кода ошибки после выполнения системного
вызова с ошибкой обязательна! Вообще говоря,
надо по крайней мере обрабатывать код ошибки
EINTR, который является признаком
того, что операция была прервана по сигналу,
тогда вызов можно повторить.
В случае с EINTR, конечно же,
можно установить для всех "безопасных"
сигналов флаг SA_RESTART, после чего
системный вызов будет автоматически повторен, но
это пример тому, что если функция вернула ошибку,
то это еще не значит, что что-то прошло неправильно.
|
if(errno == EINPROGRESS)
{
/*
* Это не ошибка, это признак
* того, что соединение все еще
* не установлено.
*/
}
else
{
/*
* Произошла ошибка сразу же
* при соединении.
*/
}
}
else
{
/*
* Соединение было установлено за время
* системного вызова, работа продолжается
* традиционным способом.
*/
}
Единственный интерес здесь представляет проверка errno
на EINPROGRESS, потому что после этого,
перед тем как начать работу с сокетом, надо
будет сначала дождаться завершения открытия соединения
(а что бы даром времени не терять, параллельно можно заняться
другими делами).
Получить информацию о том, что сокет "готов", можно используя
все тот же select(). Для этого надо установить
файловый дескриптор сокета в обоих множествах (на чтение и на запись):
fd_set rfds, wfds;
struct timeval tv;
tv.tv_sec = 0; tv.tv_usec = 500;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
int max_fd = -1;
/*
* Заполняем множества. Дескриптор
* сокета, с не открытым соединением
* помещаем в оба множества.
*/
FD_SET(fd, &wfds);
FD_SET(fd, &rfds);
if(fd > max_fd) max_fd = fd;
select(max_fd + 1, &rfds, &wfds, NULL, &tv);
Если после выполнения select() интересующий
нас дескриптор остался в каком-либо из множеств,
то соединение завершено. Остается вопрос, как
определить, не произошла ли ошибка?
if(FD_ISSET(fd, &wfds) || FD_ISSET(fd, &rfds))
{
socklen_t err_len;
int error;
err_len = sizeof(error);
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &err_len) < 0 || error != 0)
{
/*
* Произошла ошибка соединения.
*/
}
else
{
/*
* Все нормально.
*/
}
}
Теперь можно обработать ошибку или продолжить нормальное выполнение программы.
Понятно, что в случае одного сокета в программе, такой подход,
скорее всего, будет нерационален. Но при обработке большого числа
открытых сокетов с использованием select(), неблокирующий
connect() будет очень прост в реализации и, одновременно, очень
эффективен.
Обычно подпрограмму, которая работает с select() удобно
вынести в отдельный поток управления и общаться с ней
посредством очередей сокетов "на вход" и полученных данных "на выходе".
Резюме
Неблокирующий connect() является хорошим дополнением к
правильно написанному клиенту. Скорость работы с сетью может вырасти
значительно (в зависимости от того, насколько плох канал между
клиентом и сервером, а, точнее, насколько много серверов с хорошим каналом
и сколько серверов с плохим каналом). Очень прост в реализации и очень удобен:
вообще, неблокирующие операции ввода-вывода, на мой взгляд, по соотношению
скорости работы к удобству программирования самые приемлимые.
Версия для печати
| | Ссылки по теме: |
 |
| W. Richard Stevens |
| |
Unix Network programming, volume 1.
|
| W. Richard Stevens |
| |
TCP/IP Illustrated, volume 1.
|
|
 |
| W. Richard Stevens, |
| |
TCP/IP Illustrated, volume 2.
|
|
| | Рядом в разделе: |
 |
| Событийная модель в WinSock (12.12.00) |
| |
Автором этого текста является Gregory Liokumovich ( ). Он любезно прислал мне этот текст, связанный с программированием сетей при помощи интерфейса... >>>>
|
| Определение ip-адреса по имени хоста, adns (05.11.00) |
| |
Есть такой, характерный для организации "традиционного" UNIX'а, системный вызов под названием : struct hostent * gethostbyname(const char *name); Традиционен он тем,... >>>>
|
|
 |
| | Рядом по дате: |
 |
| Unix internals: the new frontiers (03.12.00) |
| |
Хочу сразу же предупредить, что эта книга, насколько мне известно, в переводе на русский язык не существует, поэтому прошу прощения, если... >>>>
|
| cpp3.virtualave.net, C++ 3rd: комментарии (26.11.00) |
| |
Ресурсы на русском языке, посвященные C++, отличаются своим количеством... существует множество сайтов, домашних страничек объединенных общей тематикой программирования на C++. Но... >>>>
|
|
| | Содержание: |
 |
|
|
| | В этом разделе: |
 |
| События ядра в FreeBSD. (16.07.01) |
| |
Обработка большого количества сетевых соединений всегда затруднительна. Мало того, не существует стандартных решений, подходящих для проблем любого вида, в которых возникает... >>>>
|
| Обзор CORBA (14.01.01) |
| |
CORBA (расшифровывается как Common Object Request Broker) это технология, которая позволяет рассматривать компоненты распределенной системы как объекты, отвечающие некоторым определенным интерфейсам.... >>>>
|
| "Тонкий" клиент (19.12.00) |
| |
В предыдущей заметке Gregory Liokumovich рассказывал о применении событийной модели WinSock для программирования сетевых приложений. На самом деле, как мне кажется,... >>>>
|
| Событийная модель в WinSock (12.12.00) |
| |
Автором этого текста является Gregory Liokumovich ( ). Он любезно прислал мне этот текст, связанный с программированием сетей при помощи интерфейса... >>>>
|
| Неблокирующий connect() (01.12.00) |
| |
В продолжение темы о замене блокирующего вызова , хочется рассказать о другой функции интерфейса сокетов, . Она имеет следующий прототип: int... >>>>
|
| Определение ip-адреса по имени хоста, adns (05.11.00) |
| |
Есть такой, характерный для организации "традиционного" UNIX'а, системный вызов под названием : struct hostent * gethostbyname(const char *name); Традиционен он тем,... >>>>
|
| lingering close (29.10.00) |
| |
Когда программа выкачивает один файл с удаленного сервера с использованием протокола TCP, а после этого сразу же "отваливается", то проблем, скорее... >>>>
|
| Содержание раздела полностью... |
| |
Примерно в тоже время |
 |
| Unix internals: the new frontiers (03.12.00) |
| |
Хочу сразу же предупредить, что эта книга, насколько мне известно, в переводе на русский язык не существует, поэтому прошу прощения, если... >>>>
|
| cpp3.virtualave.net, C++ 3rd: комментарии (26.11.00) |
| |
Ресурсы на русском языке, посвященные C++, отличаются своим количеством... существует множество сайтов, домашних страничек объединенных общей тематикой программирования на C++. Но... >>>>
|
| Хронология полностью... |
| |
Содержание |
 |
| Заглавная страница |
| Мой блог |
| Мое резюме |
| Дайджест |
| Программирование |
| |
C&C++
Сети
Unix
Алгоритмы
Оптимизация
Соревнования
Отвлеченно
XML
|
| TeX |
| Туризм |
| |
Байки
Фотографии
|
| Комментарии |
| |
Книги
Web-ресурсы
Фильмы
Интернет
Программное обеспечение
Жизнь
|
| Студенческое |
| Просто так |
| Благодарности |
| Форум |
| Хронология |
|