#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 не являются потокобезопасными.
Ваши варианты;
- Используйте ASIO (сделайте это)
- Тайм-аут при блокировании вызова. На самом деле это не переносимо, хотя может сработать.
Комментарии:
1. Это не сокеты boost, это сокеты posix. Boost — это просто убедиться, что изменение mRunning bool является атомарным. Извините, это было искажено, когда я вставлял его, и я не заметил.