Должен ли вызов WSAResetEvent после WSAEnumNetworkEvents привести к тому, что событие больше никогда не будет установлено? - PullRequest
0 голосов
/ 11 декабря 2018

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

HANDLE hEventSocket = WSACreateEvent();
WSAEventSelect(pIOParams->sock, hEventSocket, FD_READ | FD_CLOSE);
std::array<HANDLE, 2>   ahEvents;

// This is an event handle that can be called from another thread to 
// get this read thread to exit
ahEvents[0] = pIOParams->hEventStop; 
ahEvents[1] = hEventSocket;
while(pIOParams->bIsReading)
{
    // wait for stop or I/O events
    DWORD dwTimeout = 30000; // in ms
    dwWaitResult = WSAWaitForMultipleEvents(ahEvents.size(), ahEvents.data(), FALSE, dwTimeout, FALSE);
    if(dwWaitResult == WSA_WAIT_TIMEOUT)
    {
        CLogger::LogPrintf(LogLevel::LOG_DEBUG, "CSessionClient", "WSAWaitForMultipleEvents time out");
        continue;  
    }
    if(dwWaitResult == WAIT_OBJECT_0) // check to see if we were signaled to stop from another thread
    {
         break;
    }
    if(dwWaitResult == WAIT_OBJECT_0 +1)
    {
        // determine which I/O operation triggered event
        if (WSAEnumNetworkEvents(pIOParams->sock, hEventSocket, &NetworkEvents) != 0)
        {
            int err = WSAGetLastError();
            CLogger::LogPrintf(LogLevel::LOG_WARN, "CSessionClient", "WSAEnumNetworkEvents failed (%d)", err);
            break;
        }

        // HERE IS THE LINE WE REMOVED THAT SEEMED TO FIX THE PROBLEM
        WSAResetEvent(hEventSocket);

        // Handle events on socket
        if (NetworkEvents.lNetworkEvents & FD_READ)
        {
             // Do stuff to read from socket
        }
        if (NetworkEvents.lNetworkEvents & FD_CLOSE)
        {
             // Handle that the socket was closed
             break;
        }
    }

}

Вот проблема: с кодом WSAResetEvent(hEventSocket); иногда программа работает и читает все данные с сервера, но иногда кажется,чтобы застрять в цикле получения WSA_WAIT_TIMEOUT, даже если на сервере, похоже, имеются данные, поставленные в очередь для него.

Пока программа выполняет цикл приема WSA_WAIT_TIMEOUT, Process Hacker показывает сокет, подключенный в нормальном состоянии.

Теперь мы знаем, что WSAEnumNetworkEvents сбросит hEventSocket, но не похоже, что дополнительный вызов WSAResetEvent должен повредить.Также не имеет смысла, что это постоянно портит сигнализацию.Я ожидаю, что, возможно, мы не получим уведомление о последнем фрагменте данных для чтения, так как данные могли быть прочитаны между вызовами WSAEnumNetworkEvents и WSAResetEvent, но я предполагаю, что, как только дополнительные данные поступятв сокете hEventSocket будет поднят.

Странной частью этого является то, что мы выполняем этот код в течение многих лет, и мы только сейчас видим эту проблему.

Есть идеи, почему это может вызвать проблемы?

Ответы [ 2 ]

0 голосов
/ 13 декабря 2018

Вызов WSAResetEvent() вручную вводит состояние гонки, которое может перевести ваш сокет в плохое состояние.

После вызова WSAEnumNetworkEvents(), когда новые данные поступают впоследствии, или остаются непрочитанные данные отранее прочитано, затем событие сигнализируется, но ТОЛЬКО если сокет находится в собственном состоянии, чтобы сигнализировать об этом событии.

Если событие действительно сигнализируется до того, как вы позвоните WSAResetEvent(), выпотерять этот сигнал.

В соответствии с документацией WSAEventSelect():

Успешно записав возникновение сетевого события (установив соответствующий бит во внутреннейзапись сетевого события) и сигнализировал связанный объект события, никаких дальнейших действий для этого сетевого события не предпринимается, пока приложение не выполнит вызов функции, которая неявно повторно активирует настройку этого сетевого события и сигнализирует о связанном объекте события .

FD_READ Функции recv, recvfrom, WSARecv, WSARecvEx или WSARecvFrom.

...

Любой вызов подпрограммы повторного включения, даже тот, который не удался, приводит к повторному включению записи и сигнализации для соответствующего сетевого события и объекта события .

...

Для сетевых событий FD_READ, FD_OOB и FD_ACCEPT запись сетевого события и сигнализация объекта события инициируются на уровне.Это означает, что , если вызывается подпрограмма повторного включения и соответствующее условие сети остается действительным после вызова, сетевое событие записывается и устанавливается связанный объект события.Это позволяет приложению быть управляемым событиями и не беспокоиться о количестве данных, поступающих в любой момент времени .

Это означает, что если вы вручную сбросите событие после вызова WSAEnumNetworkEvents(), событие НЕ будет снова сигнализировано, пока ПОСЛЕ того, как вы выполните чтение в сокете (что снова включает подписьсобытие для операций чтения) И новые данные поступают позже, или вы не прочитали все доступные данные.

Сбрасывая событие вручную, вы теряете сигнал, который позволяет WSAWaitForMultipleEvents() сообщить вамчтобы позвонить WSAEnumNetworkEvents(), чтобы он мог затем сказать вам читать из сокета.Без этого чтения событие никогда не будет сигнализировано снова, когда данные ожидают чтения.Единственное зарегистрированное условие, которое может сигнализировать о событии, - это закрытие сокета.

Поскольку WSAEnumNetworkEvents() уже сбрасывает событие для вас, НЕ сбрасывайте событие вручную!

0 голосов
/ 11 декабря 2018

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

При прямом вызове WSAResetEvent может быть потеряно уведомление о данных (то есть вы вызываете WSAEnumNetworkEvents, чтобы получить текущий статус и сбросить событие, после которого поступает больше данных, после чего событие устанавливается, но до этогоВы вызываете WSAResetEvent, затем вызываете WSAResetEvent до следующей итерации цикла, и если больше данных не поступит, вам не сообщат о уже поступивших данных.

Гораздо лучше просто позволить WSAEnumNetworkEvents иметь дело с событиемсостояние.

...