Использование потоковых уведомлений с сокетами Беркли

Я использую функцию выбора сокетов Беркли следующим образом.

/*Windows and linux typedefs/aliases/includes are made here with wsa
junk already taken care of.*/

/**Check if a socket can receive data without waiting.
\param socket The os level socket to check.
\param to The timeout value. A nullptr value will block forever, and zero
for each member of the value will cause it to return immediately.
\return True if recv can be called on the socket without blocking.*/
bool CanReceive(OSSocket& socket,
const timeval * to)
{
fd_set set = {};
FD_SET(socket, &set);
timeval* toCopy = nullptr;
if (to)
{
toCopy = new timeval;
*toCopy = *to;
}

int error = select((int)socket, &set, 0, 0, toCopy);
delete toCopy;
if (error == -1)
throw Err(); //will auto set from errno.
else if (error == 0)
return false;
else
return true;
}

Я написал класс, который будет наблюдать за контейнером сокетов (завернутым в другом классе) и добавлять идентификатор в отдельный контейнер, в котором хранится информация о том, какие сокеты готовы к доступу. Карта является неупорядоченной_картой.

while(m_running)
{
for(auto& e : m_idMap)
{
auto id = e.first;
auto socket = e.second;
timeval timeout = ZeroTime; /*0sec, 0micro*/
if(CanReceive(socket,&timeout) &&
std::count(m_readyList.begin(),m_readyList.end(),socket) == 0)
{
/*only add sockets that are not on the list already.*/
m_readyList.push_back(id);
}
}
}

Как я уверен, многие заметили, что этот код работает безумно быстро и поглощает процессор, как будто завтрашнего дня нет (загрузка ЦП 40% только с одним сокетом на карте). Мое первое решение состояло в том, чтобы иметь интеллектуальную функцию ожидания, которая поддерживает количество повторений в секунду до заданного значения. Казалось, что это хорошо для некоторых людей. У меня такой вопрос: как я могу получить уведомление о готовности сокетов без использования этого метода? Даже если для сохранения переносимости может потребоваться куча макрокоманд, это нормально. Я могу только думать, что может быть какой-то способ заставить операционную систему наблюдать за мной и получать какое-то уведомление или событие, когда сокет будет готов. Просто чтобы прояснить, я решил не использовать точечную сеть.

Цикл работает в своем собственном потоке, отправляет уведомления другим частям программного обеспечения, когда сокеты готовы. Все это является многопоточным, и каждая его часть (кроме этой части) использует систему уведомлений на основе событий, которая устраняет проблему занятого ожидания. Я понимаю, что все становится зависимым от ОС и ограниченным в этой области.

Редактировать: сокеты запускаются в режиме BLOCKING (но у select нет времени ожидания, и поэтому они не будут блокироваться), но они работают в выделенном потоке.
Редактировать: система работает отлично с интеллектуальными спящими функциями, но не так хорошо, как с некоторой системой уведомлений (вероятно, из ОС).

0

Решение

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

Во-вторых, лучший способ сделать это варьируется от платформы к платформе. Если вы не хотите писать много кода для конкретной платформы, вам действительно следует использовать такую ​​библиотеку, как Boost ASIO или libevent.

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

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

1

Другие решения

Как я могу получить уведомление о готовности сокетов, не используя это?
метод?

Вот для чего select (). Идея состоит в том, что ваш вызов select () должен блокироваться, пока хотя бы один из сокетов, которые вы ему передали (через FD_SET ()), не будет готов для чтения. После возврата из select () вы можете узнать, какие сокеты теперь готовы для чтения (вызывая FD_ISSET ()) и вызывая recv () для этих сокетов, чтобы получить от них некоторые данные и обработать их. После этого вы снова зацикливаетесь, снова возвращаетесь в режим сна внутри select () и повторяете до бесконечности. Таким образом, вы выполняете все свои задачи как можно быстрее, используя при этом минимальное количество циклов ЦП.

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

Обратите внимание, что если ваш поток заблокирован внутри select (), и вы хотите, чтобы он проснулся и сделал что-то сразу (т.е. не полагаясь на тайм-аут, который был бы медленным и неэффективным), вам понадобится какой-то способ вызвать select () в этой теме, чтобы вернуться немедленно. По моему опыту, самый надежный способ сделать это — создать pipe () или socketpair () и сделать так, чтобы поток включал один конец пары file-descriptor в готовый для чтения fd_set. Затем, когда другой поток хочет разбудить этот поток, он может сделать это, просто отправив байт на другой конец пары. Это приведет к возврату select (), поток может затем прочитать один байт (и выбросить его), а затем сделать все, что он должен делать после пробуждения.

0

По вопросам рекламы [email protected]