Правильный способ закрыть блокирующий сокет UDP

#c #sockets

#c #сокеты

Вопрос:

У меня есть объект C , который создает поток для чтения из блокирующего сокета UDP:

 mRunning.store(true);
while (mRunning.load(boost::memory_order_consume)) {
    ...
    int size = recvfrom(mSocket, buf, kTextBufSize , 0,
                        (struct sockaddr *) amp;packet->mReplyAddr.mSockAddr, (socklen_t*)amp;packet->mReplyAddr.mSockAddrLen);

    if (size > 0) {
        //do stuff
    }
}
return 0;
  

(mRunning — это повышение ::atomic)
Деструктор объекта вызывается из другого потока и выполняет следующее:

 mRunning.store(false);  
#ifdef WIN32
    if (mSocket != -1) closesocket(mSocket);
#else
    if (mSocket != -1) close(mSocket);
#endif
pthread_join(mThread, NULL);
  

Кажется, это работает, но один из моих коллег предположил, что может возникнуть проблема, если recv прерывается в середине чтения чего-либо. Является ли это потокобезопасным? Каков правильный способ закрытия блокирующего сокета UDP? (Должен быть кроссплатформенным OSX / Linux / Windows)

Ответ №1:

Может возникнуть много разных проблем. Перемещая мое приложение с одной версии FreeBSD на другую, я обнаружил, что такая close () нормально работала на более старом ядре и просто зависала close (), пока что-то не вернулось из recv () на более новом. И OSX основана на FreeBSD 🙂

Переносимый способ закрытия сокетов из другого потока — создать канал и блокировать не в recv(), а в select(). Когда вам нужно закрыть сокет, напишите что-нибудь в канал, select () разблокируется, и вы можете безопасно выполнить close().

Комментарии:

1. Я не думаю, что это будет работать кроссплатформенно на самом деле, поскольку select () в Windows, похоже, поддерживает только сокеты, а не каналы.

2. В Windows у вас может быть отдельный код, который будет использовать WSAEventSelect() и WaitForMultipleObjects().

Ответ №2:

Ну, recvfrom сам по себе он потокобезопасен. IIRC все функции сокета являются. Вопрос в том:

  • Что произойдет, если вы вытащите дескриптор из-под recvfrom во время копирования данных в ваш буфер?

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

  • Завершите операцию (возможно, потому, что ей больше не нужен дескриптор, или потому, что она выполняет какой-то классный подсчет ссылок или что-то еще)
  • Вызвать recvfrom сбой и вернуть -1 ( ENOTSOCK , EINVAL ?)
  • Впечатляющий сбой, потому что буферы и внутренние структуры данных освобождаются close .

Очевидно, что это всего лишь предположение (я ошибался раньше, много раз), но если вы не найдете в стандарте чего-либо, подтверждающего идею о том, что вы можете закрыть сокет во время приема через него, вы не в безопасности.

Итак, что вы можете сделать? Самый безопасный: используйте механизм синхронизации, чтобы гарантировать, что вы используете сокет только close после recvfrom завершения (семафоры, мьютексы, что угодно).

Лично я бы сделал UP на семафоре после recvfrom и DOWN перед close .

Ответ №3:

Ваш коллега прав, сокеты boost не являются потокобезопасными.

Ваши варианты;

  1. Используйте ASIO (сделайте это)
  2. Тайм-аут при блокировании вызова. На самом деле это не переносимо, хотя может сработать.

Комментарии:

1. Это не сокеты boost, это сокеты posix. Boost — это просто убедиться, что изменение mRunning bool является атомарным. Извините, это было искажено, когда я вставлял его, и я не заметил.