select on UDP socket не заканчивается при закрытии сокета - что я делаю не так? - PullRequest
8 голосов
/ 19 января 2009

Я работаю в системе Linux (сервер Ubuntu 7.04 с ядром 2.6.20).

У меня есть программа, в которой есть поток (thread1), ожидающий выбора, чтобы сокет UDP стал читабельным. Я использую select (с моим сокетом в качестве единственного readfd и единственного кромеfd) вместо того, чтобы просто вызывать recvfrom, потому что я хочу тайм-аут.

Из другого потока отключаю и закрываю сокет. Если я сделаю это, когда thread1 заблокирован в recvfrom, то recvfrom немедленно прекратит работу. Если я сделаю это, в то время как thread1 заблокирован в выборе с тайм-аутом, тогда выбор НЕ завершится немедленно, но в конечном итоге истечет правильно.

Может кто-нибудь сказать мне, почему выбор не завершается, как только сокет закрывается? Разве это не исключение? Я вижу, где он не читается (очевидно), но он закрыт, что кажется исключительным.

Вот открытие сокета (вся обработка ошибок убрана для простоты):

m_sockfd = socket(PF_INET, SOCK_DGRAM, 0);
struct sockaddr_in si_me;
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(port);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(m_sockfd, (struct sockaddr *)(&si_me), sizeof(si_me)) < 0)
{
// deal with error
}

Вот оператор выбора, который выполняет thread1:

struct timeval to;
to.tv_sec = timeout_ms/1000;// just the seconds portion
to.tv_usec = (timeout_ms%1000)*1000;// just the milliseconds 
                                    // converted to microseconds

// watch our one fd for readability or
// exceptions.
fd_set  readfds, exceptfds;
FD_ZERO(&readfds);
FD_SET(m_sockfd, &readfds);
FD_ZERO(&exceptfds);
FD_SET(m_sockfd, &exceptfds);

int nsel = select(m_sockfd+1, &readfds, NULL, &exceptfds, &to);

ОБНОВЛЕНИЕ: Очевидно (как указано ниже), закрытие сокета не является исключительным условием (с точки зрения select). Я думаю, что мне нужно знать: почему? И это намеренно?

Я ДЕЙСТВИТЕЛЬНО хочу понять, что стоит за этим избранным поведением, потому что оно противоречит моим ожиданиям. Таким образом, мне, очевидно, необходимо скорректировать свое мышление о том, как работает стек TCP. Пожалуйста, объясни мне это.

Ответы [ 6 ]

4 голосов
/ 26 января 2009

Может быть, вы должны использовать что-то еще, чтобы разбудить выбор. Может быть, труба или что-то в этом роде.

4 голосов
/ 19 января 2009

UDP - это протокол без установления соединения. Так как нет соединения, ни одно не может быть разорвано, поэтому потребитель не знает, что производитель больше никогда не отправит.

Вы могли бы сделать так, чтобы производитель отправил сообщение «конец потока», и чтобы получатель завершил работу после его получения.

3 голосов
/ 27 июля 2009

Не могли бы вы отправить поток (например, USR2) потоку, который заставил бы select () вернуться с EINTR? Затем в обработчике сигналов установите флажок, запрещающий перезапускать select ()?

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

2 голосов
/ 26 января 2009

Я бы сказал, что разница в том, что recvfrom активно пытается прочитать сообщение из одного сокета, где select ожидает прибытия сообщения, возможно, с несколькими дескрипторами, а не обязательно с дескрипторами сокетов.

2 голосов
/ 22 января 2009

Я думаю, что наиболее очевидным решением является то, что закрытие не считается исключительным условием. Я думаю, что корень проблемы в том, что вы на самом деле не придерживаетесь философии select. С какой стати вы возитесь с розеткой в ​​другом потоке, это звучит как рецепт катастрофы.

0 голосов
/ 24 января 2017

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

Когда вы собираетесь закрыть сокет, просто невозможно узнать, заблокирован ли другой поток в select или собирается заблокировать в select. Например, рассмотрим следующее:

  1. Поток отправляется на вызов select, но не назначается по расписанию.
  2. Вы закрываете сокет.
  3. В потоке, о котором ваш код не знает (может быть, это часть внутренней памяти управления платформой или внутренней системы ведения журнала), библиотека открывает сокет и получает тот же идентификатор, что и сокет, который вы закрыли.
  4. Теперь поток переходит в select, но select находится в сокете, открытом библиотекой.
  5. Бедствие.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...