Что делает select() и FD_ISSET()?

286
15 декабря 2016, 16:16
    #ifdef WIN32
        int iMode = 1;
        ioctlsocket( m_Socket, FIONBIO, (u_long FAR *)&iMode );
    #else
        fcntl( m_Socket, F_SETFL, fcntl( m_Socket, F_GETFL ) | O_NONBLOCK );
    #endif
    fd_set fd;
        FD_ZERO( &fd );
        FD_SET( m_Socket, &fd );
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 0;
        // check if the socket is connected
    #ifdef WIN32
        if( select( 1, NULL, &fd, NULL, &tv ) == SOCKET_ERROR )
    #else
        if( select( m_Socket + 1, NULL, &fd, NULL, &tv ) == SOCKET_ERROR )
    #endif
        {
            m_HasError = true;
            m_Error = GetLastError( );
            return false;
        }
        if( FD_ISSET( m_Socket, &fd ) )
        {
            m_Connecting = false;
            m_Connected = true;
            return true;
        }

Не пойму, что происходит с select и FD_ISSET? Кого и как select оповещает о доступных данных? И зачем FD_ISSET проверяет, есть ли сокет в наборе, ведь он же явно там есть, так как FD_SET вызывался. Получается, что select сразу возвращает управление, но при этом операционная система следит за тем, есть ли данные для сокета, и если нету, то удаляет сокет из набора, а если есть, то добавляет снова, поэтому и нужен FD_ISSET?

Answer 1

Для начала нужно понять, что такое fd_set fd; - это просто массив (почти, см. ниже) на 1024 элемента (в некоторых случаях там может быть другое число, закладываться на него не нужно).

typedef struct fd_set {
  unsigned int count;
  int fd[FD_SETSIZE];
} fd_set;

Индекс этого массива - это просто номер сокета (в той реализации, которую я нашел, сделано наоборот - значение это сокет, но это не меняет сути. Разные оси по-разному оптимизируют). Функция (макрос) FD_SET просто выставляет флажок в этом массиве. FD_ZERO просто снимает все флажки. FD_ISSET просто проверяет наличие флажка для заданного номера сокета.

А теперь главное. Что делает select. Он получает несколько таких наборов. То есть есть набор номеров сокетов и ожидаемые действия от них. Селект следит за данными сокетами, и если по ним происходят изменения, то он модифицирует массивы (не забываем, их там три). А потом просто возвращает количество модификаций.

И вот теперь с помощью FD_ISSET можно проверить, были ли изменения по данному сокету (проверять-то нужно в каждом массиве, так как разные массивы для разных действий).

Кого и как select оповещает о доступных данных?

Ваш код. Потому как Ваш код проверяет результат.

И зачем FD_ISSET проверяет, есть ли сокет в наборе, ведь он же явно там есть, так как FD_SET вызывался.

В том то и дело, что его там может и не быть (если по нему не было заданных изменений).

Получается, что select сразу возвращает управление, но при этом операционная система следит за тем, есть ли данные для сокета, и если нету, то удаляет сокет из набора, а если есть, то добавляет снова, поэтому и нужен FD_ISSET?

select не сразу возвращает управление. Он возвращает управление в трех случаях

  • хотя бы на одном сокете произошли изменения;
  • пришел заданный тайм-аут;
  • пришел сигнал (да, поэтому может такое быть, что и тайм-аут не истек, и сокеты не изменились, а select завершился). Типичный сигнал - Ctrl+C.

После выхода с select'а набор уже может быть модифицирован.

Обновление

Есть две реализации fd_set. В той реализации, которую я нашел прям сейчас - fd_set просто добавляет в массив номер сокета (даже без проверки, что он там уже есть) и увеличивает счетчик сокетов в массиве.

В этой реализации он просто удаляет из массива те номера дескрипторов, которые не изменялись, и оставляет в массиве те номера, по которым были изменения.

FD_ISSET в этой реализации просто ищет, есть ли в массиве данный дескриптор.

Если тайм-ауты нулевые, то select просто проверит состояние сокетов и выставит правильно номера дескрипторов в массиве и сразу выйдет.

Обновление 2

Этих действий не много. Это есть данные для чтения, есть данные для записи, есть внеочередные данные (это особое, редко используется), сокет закрылся для записи, сокет закрылся для чтения (да, можно делать полузакрытый сокет).

и выставит правильно номера дескрипторов в массиве

select смотрит на номера сокетов, которые ему передали и следит за их изменениями. Когда изменения есть (или наступил тайм-аут), то он собирает список измененных сокетов и записывает их в массив (а сам массив перед этим почистит).

Я посмотрел детальнее. Реализация, где индексы - дескрипторы - это линуксовое. Где дескрипторы записываются в массив по значению - это виндовое. Кстати, по этой причине в виндовс медленный select. Но зато у него нет одной проблемы, которая есть в линуксе - если номер сокета больше 1023, то линуксовый select не будет работать (просто пишется за пределы массива). В виндовс формально сокеты не имеют номера (хотя его имеют, но это деталь реализации), и select будет работать с любыми сокетами, пока их общее количество не превысит максимума.

READ ALSO
Конструктор класса

Конструктор класса

Доброго времени суток

209
Считывание строки

Считывание строки

На вход подается описание бинарного дереваНа листьях (висячих вершинах) этого дерева написаны целые числа (от -10^6 до 10^6)

255
Проблема с наследованием C++, среда Borland Builder 6.0

Проблема с наследованием C++, среда Borland Builder 6.0

Есть такая проблема: имеется иерархия классовВ производных появляются новые методы

238