Событие Winsock accept иногда прекращает сигнализацию (WSAEventSelect) - PullRequest
1 голос
/ 29 января 2010

У меня проблема с частью устаревшего кода на c ++ / winsock, который является частью многопоточного сервера сокетов. Приложение создает поток, который обрабатывает соединения от клиентов, которых обычно одновременно подключено несколько сотен. Обычно он работает без проблем в течение нескольких дней (непрерывно), а затем внезапно прекращает принимать подключения. Это происходит только в производстве, никогда не тестировать.

Он использует WSAEventSelect () для обнаружения сетевых событий FD_ACCEPT. (Упрощенный) код для обработчика соединения:

SOCKET listener;
HANDLE hStopEvent;

// ... initialise listener and hStopEvent, and other stuff ...

HANDLE hAcceptEvent = WSACreateEvent();
WSAEventSelect(listener, hAcceptEvent, FD_ACCEPT); 
HANDLE rghEvents[] = { hStopEvent, hAcceptEvent };

bool bExit = false;
while(!bExit)
{
    DWORD nEvent = WaitForMultipleObjects(2, rghEvents, FALSE, INFINITE);
    switch(nEvent)
    {
        case WAIT_OBJECT_0:
            bExit = true;
            break;
        case WAIT_OBJECT_1:
            HandleConnect();
            WSAResetEvent(hAcceptEvent);
            break;
        case WAIT_ABANDONED_0:
        case WAIT_ABANDONED_0 + 1:
        case WAIT_FAILED:
            LogError();
            break;
    }
}

Из подробного ведения журнала я знаю, что при возникновении проблемы поток входит в WaitForMultipleObjects () и никогда не появляется, даже если есть клиенты, пытающиеся подключиться и ожидающие принятия. Условия WAIT_FAILED и WAIT_ABANDONED_x никогда не выполняются.

Хотя я не исключил проблему с конфигурацией на сервере или даже какую-то утечку ресурсов (ничего не могу найти), мне также интересно, не происходит ли диссоциация с событием, созданным WSACreateEvent (). 'из сетевого события FD_ACCEPT - вызывая его никогда не срабатывать.

Так, я что-то здесь не так делаю? Есть ли что-то, что я должен делать, а я нет? Или лучше? Буду признателен за любые предложения! Благодаря.

EDIT

Розетка - это неблокирующая розетка.

EDIT

Проблема решена с помощью подхода, предложенного Кипкеннеди (ниже). Изменил hAcceptEvent на событие автоматического сброса и удалил вызов WSAResetEvent (), который больше не нужен.

Ответы [ 4 ]

4 голосов
/ 02 февраля 2010

Может быть, FD_ACCEPT сигнализирует во время HandleConnect () после accept () и перед return и последующим ResetEvent (). Затем ResetEvent () завершает сброс всех сигналов, и повторное включение accept () не вызывается. Например, возможна следующая последовательность:

  1. Событие сигнализирует, WaitForMultipleObjects () возвращает
  2. Во время HandleConnect (), спустя некоторое время после вызова accept (), событие снова сигнализируется
  3. HandleConnect () возвращает
  4. ResetEvent () сбрасывает событие, маскируя второй сигнал
  5. WaitForMultipleObjects () никогда не возвращается, поскольку, что касается Windows, она уже сигнализировала о последующем событии, и никакие последующие accept () не повторно включили его

Пара возможных решений: 1) зацикливание на accept () в HandleConnect () до тех пор, пока не будет возвращено WSAEWOULDBLOCK 2) использовать событие автоматического сброса или немедленно сбросить событие перед вызовом HandleConnect ()

1 голос
/ 16 марта 2010

После того, как произошло событие принятия, вы не должны делать WSAResetEvent (hAcceptEven). Вы должны выполнить WSAEnumNetworkEvents (listener, hAcceptEvent и some_struct). Эта функция очищает внутреннее состояние сокета (ar копирует это состояние в some_struct), и после этого вы можете получать новые соединения.

1 голос
/ 30 января 2010

Из чтения документов выясняется, что WSAEventSelect() столь же экономен в отношении уведомлений, как и WSAAsyncSelect(). Стек не сигнализирует FD_ACCEPT каждый раз, когда устанавливается соединение. Для Winsock уведомление - это способ сказать:

Вы звонили accept() ранее, и это не удалось с WSAEWOULDBLOCK. Продолжайте и позвоните снова, на этот раз все получится.

Решение состоит в том, чтобы позвонить accept() до вызова WSAEventSelect(), и звонить WSAEventSelect() только после получения WSAEWOULDBLOCK. Чтобы это работало так, как вы ожидаете, вы должны установить прослушивающий сокет в неблокирующее состояние. (Это может показаться очевидным, но на самом деле это не требуется.)

1 голос
/ 29 января 2010

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

...