Неожиданный WSA_IO_PENDING от блокировки (с перекрывающимся атрибутом ввода / вывода) вызовы Winsock2 - PullRequest
0 голосов
/ 20 сентября 2018

Короткая версия : я получаю WSA_IO_PENDING при использовании блокирующих вызовов API сокетов.Как мне справиться с этим?Сокет имеет перекрывающийся атрибут ввода-вывода и устанавливается с таймаутом.

Длинная версия :

Платформа : Windows 10. Visual Studio 2015

Гнездо создано очень традиционным простым способом.

s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

Сокет имеетПерекрытый по умолчанию ввод / вывод Атрибут включен.Это можно проверить с помощью getsockop / SO_OPENTYPE .

  • Мне нужен атрибут с перекрытием, потому что я хочу использовать функцию тайм-аута, например, SO_SNDTIMEO .
  • И я бы использовал сокет только в режиме блокировки (т. Е. Синхронно).
  • операция чтения сокета выполняется только в пределах одного потока.
  • операция записи в сокет можетбыть выполненным из разных потоков, синхронизированных с мьютексом.

Сокет активируется по таймауту и ​​поддерживается с ...

::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, ...);

::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, ...);

::WSAIoctl(s, SIO_KEEPALIVE_VALS, ...);

Операции с сокетом выполняются с

::send(s, sbuffer, ssize, 0); и

::recv(s, rbuffer, rsize, 0);

Я также пытаюсьиспользовать WSARecv и WSASend с обоими значениями lpOverlapped и lpCompletionRoutine, установленными в NULL.

[MSDN] ... Если и lpOverlapped, и lpCompletionRoutineNULL, сокет в этой функции будет рассматриваться как не перекрывающийся сокет.

::WSARecv(s, &dataBuf, 1, &nBytesReceived, &flags, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)

::WSASend(s, &dataBuf, 1, &nBytesSent, 0, NULL/*lpOverlapped*/, NULL/*lpCompletionRoutine*/)

Проблема :

Эти вызовы блокировки отправки / recv / WSARecv / WSASend вернут ошибку с WSA_IO_PENDING код ошибки!

Вопросы :

Q0: есть ли ссылка на перекрывающийся атрибут с блокирующим вызовом и тайм-аутом?

Как это ведет себя?в случае, если у меня есть сокет с перекрывающимся «атрибутом» + функция тайм-аута, и я просто использую блокирующий API сокета с «не перекрывающейся семантикой ввода-вывода».

Я не смог найти никакой ссылки об этом (например,из MSDN).

Q1: это ожидаемое поведение?

Я наблюдал эту проблему (получить WSA_IO_PENDING) после переноса кода из Win XP / Win 7 в Win 10 .

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

    auto s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    assert(s != INVALID_SOCKET);

    timeval timeout;
    timeout.tv_sec = (long)(1500);
    timeout.tv_usec = 0;

    assert(::setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);

    assert(::setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout)) != SOCKET_ERROR);

    struct tcp_keepalive
    {
      unsigned long onoff;
      unsigned long keepalivetime;
      unsigned long keepaliveinterval;
    } heartbeat;
    heartbeat.onoff             = (unsigned long)true;                         
    heartbeat.keepalivetime     = (unsigned long)3000;
    heartbeat.keepaliveinterval = (unsigned long)3000;
    DWORD nob = 0;

    assert(0 == ::WSAIoctl(s, SIO_KEEPALIVE_VALS, &heartbeat, sizeof(heartbeat), 0, 0, &nob, 0, 0));

    SOCKADDR_IN connection;
    connection.sin_family = AF_INET;
    connection.sin_port = ::htons(port);
    connection.sin_addr.s_addr = ip;

    assert(::connect(s, (SOCKADDR*)&connection, sizeof(connection)) != SOCKET_ERROR);

    char buffer[100];
    int receivedBytes = ::recv(s, buffer, 100, 0);

    if (receivedBytes > 0)
    {
      // process buffer
    }
    else if (receivedBytes == 0)
    {
      // peer shutdown
      // we will close socket s
    }
    else if (receivedBytes == SOCKET_ERROR)
    {
      const int lastError = ::WSAGetLastError();
      switch (lastError)
      {
      case WSA_IO_PENDING:
          //.... I get the error!
      default:
      }
    }

Q2: Как мне справиться с этим?

Игнорировать это?или просто закрыть сокет как обычный случай ошибки?

Из наблюдения, как только я получу WSA_IO_PENDING, и если я просто проигнорирую его, сокет в конечном итоге перестанет отвечать на запросы ..

Q3: Как насчет WSAGetOverlappedResult ?

это имеет какой-то смысл?

Какой объект WSAOVERLAPPED я должен дать?Поскольку такого я не использую для всех этих блокирующих вызовов сокетов.

Я попытался просто создать новый пустой WSAOVERLAPPED и использовать его для вызова WSAGetOverlappedResult.Это в конечном счете вернется с успехом с 0 переданным байтом.

1 Ответ

0 голосов
/ 20 сентября 2018

Q3: Как насчет WSAGetOverlappedResult?

в [WSA]GetOverlappedResult, мы можем использовать только указатель на WSAOVERLAPPED, переданный на I / O .Использовать любой другой указатель бессмысленно.вся информация о I / O операция WSAGetOverlappedResult получить от lpOverlapped (конечный статус, количество переданных байтов, если нужно подождать - это ожидание по событию из этого перекрытия).в общих словах - каждый I / O запрос должен передавать OVERLAPPED (IO_STATUS_BLOCK реально) указатель на ядро.память прямого изменения ядра (окончательное состояние и информация (обычно передаваемые байты). потому что это время жизни OVERLAPPED должно быть действительным до тех пор, пока I / O не завершится. и должно быть уникальным для каждого I / O) запрос. [WSA]GetOverlappedResult проверка этой памяти OVERLAPPED (IO_STATUS_BLOCK действительно) - прежде всего ищите статус. Если это другой из STATUS_PENDING - это означает, что операция завершена - api принимает количество переданных байтов ивозврат. если все еще STATUS_PENDING здесь - I/O еще не завершено. если мы хотим подождать - API использует hEvent из перекрывающихся для ожидания. Этот дескриптор события передается ядру во время запроса I / O ибудет установлен в состояние сигнала, когда I / O закончен. Ожидать любое другое событие бессмысленно - как это связано с конкретным I / O запросом? Думаю, теперь должно быть понятно, почему мы можемвызовите [WSA]GetOverlappedResult только с точно перекрывающимся указателем, переданным на I / O запрос.

, если мы не передадим указатель на OVERLAPPED самостоятельно (например, если мы используемrecv или send) розетка низкого уровняapi - самостоятельно выделите OVERLAPPED как локальную переменную в стеке и передайте ей указатель на I / O .как результат - API не может вернуться в этом случае, пока I / O не завершен.потому что перекрывающаяся память должна быть действительной до тех пор, пока I / O не будет завершена (при завершении ядро ​​записывает данные в эту память).но локальная переменная стала недействительной после выхода из функции.поэтому функция должна ждать на месте.

потому что все это мы не можем назвать [WSA]GetOverlappedResult после send или recv - сначала у нас просто нет указателя на перекрытие.в секунду перекрытие используется в запросе I / O , который уже «уничтожен» (точнее, в стеке ниже вершины - то есть в мусорной зоне).если I / O еще не завершено - ядро ​​уже модифицирует данные в стеке случайных мест, когда оно окончательно завершится - это будет иметь непредсказуемый эффект - из ничего не произойдет - к аварийному завершению или очень необычным побочным эффектам.если send или recv вернутся до завершения I / O - это будет иметь фатальный эффект для процесса.это никогда не должно быть (если нет ошибки в Windows).

В2: Как мне с этим справиться?

Как мне объяснить, действительно ли WSA_IO_PENDING вернул send или recv - это системная ошибка.хорошо, если I / O завершено устройством с таким результатом (несмотря на то, что он не должен) - просто какой-то неизвестный (для такой ситуации) код ошибки.обрабатывать это как любую общую ошибку.не требует специальной обработки (как в случае асинхронного ввода-вывода).если I / O действительно еще не завершено (после send или recv возвращено) - это означает, что в случайное время (возможно, уже) ваш стек может быть поврежден.Эффект от этого непредсказуем.и тут ничего не поделаешь.это критическая системная ошибка.

Q1: ожидается ли это поведение?

нет, это абсолютно не исключено.

Q0: есть ли ссылка на перекрывающийся атрибут с блокирующим вызовом и тайм-аутом?

Прежде всего, когда мы создаем дескриптор файла, мы устанавливаем или не устанавливаем для него асинхронный атрибут: inкейс CreateFileW - FILE_FLAG_OVERLAPPED, кейс WSASocket - WSA_FLAG_OVERLAPPED.в случае NtOpenFile или NtCreateFile - FILE_SYNCHRONOUS_IO_[NO]NALERT (обратный эффект сравнить FILE_FLAG_OVERLAPPED).вся эта информация, хранящаяся в FILE_OBJECT.Flags - FO_SYNCHRONOUS_IO ( Файловый объект открывается для синхронного ввода / вывода. ) будет установлен или очищен.

Эффект флага FO_SYNCHRONOUS_IO следующий: I / O подсистема вызывает некоторый драйвер через IofCallDriver, и если драйвер возвращает STATUS_PENDING - в случае, если FO_SYNCHRONOUS_IO флаг установлен в FILE_OBJECT - ждатьна месте (как и в ядре), пока I / O не завершится.в противном случае верните этот статус - STATUS_PENDING для вызывающего абонента - он может ждать себя на месте или обратного вызова получателя через APC или IOCP .

когда мы используем socket it внутренний вызов WSASocket -

Созданный сокет будет иметь перекрывающийся атрибут по умолчанию

этот средний файл не будет иметь атрибута FO_SYNCHRONOUS_IO и низкий уровень I / O вызовы могут вернуть STATUS_PENDING из ядра.но давайте посмотрим, как работает recv:

внутренне WSPRecv вызывается с lpOverlapped = 0.потому что это - WSPRecv Вы сами выделяете OVERLAPPED в стеке как локальную переменную.прежде чем сделать фактический I / O запрос через ZwDeviceIoControlFile.поскольку файл (сокет) создан без флага FO_SYNCHRONOUS - STATUS_PENDING возвращается из ядра.в этом случае WSPRecv выглядят - lpOverlapped == 0.если да - он не может вернуться, пока операция не завершена.начинается ожидание события (внутреннее ведение в пользовательском режиме для этого сокета) через SockWaitForSingleObject - ZwWaitForSingleObject.вместо Timeout используется значение, которое вы связали с сокетом через SO_RCVTIMEO или 0 (бесконечное ожидание), если вы не установили SO_RCVTIMEO.если ZwWaitForSingleObject вернуть STATUS_TIMEOUT (это может быть только в том случае, если вы установили таймаут с помощью SO_RCVTIMEO) - это означает, что операция I / O не завершена в течение ожидаемого времени.в этом случае WSPRecv называется SockCancelIo (тот же эффект, что и CancelIo).CancelIo не должен возвращать (ждать), пока все I / O запрос на файл (из текущего потока) не будет завершен.после этого WSPRecv читать окончательный статус из перекрывающихся.здесь должно быть STATUS_CANCELLED (но на самом деле конкретный драйвер решает, какой статус завершения отменен IRP).WSPRecv конвертирует STATUS_CANCELLED в STATUS_IO_TIMEOUT.затем вызовите NtStatusToSocketError для преобразования кода ntstatus в ошибку win32.скажем STATUS_IO_TIMEOUT преобразован в WSAETIMEDOUT.но если все еще был STATUS_PENDING в перекрытии, после CancelIo - вы получили WSA_IO_PENDING.только в этом случае.Похоже на ошибку устройства, но я не могу воспроизвести ее на собственном win 10 (может быть, роль в версии)


что здесь можно сделать (если вы уверены, что действительно получили WSA_IO_PENDING)?сначала попробуйте использовать WSASocket без WSA_FLAG_OVERLAPPED - в этом случае ZwDeviceIoControlFile никогда не вернет STATUS_PENDING, и вы никогда не должны получить WSA_IO_PENDING.проверить это - ошибка ушла?если да - верните перекрывающийся атрибут и удалите вызов SO_RCVTIMEO (все это для теста - не решение для продукта выпуска) и проверьте, устранена ли эта ошибка.если да - похоже на недействительное устройство отмены (с STATUS_PENDING?!?) IRP .смысл всего этого - найдите где ошибка конкретнее.В любом случае интересно будет построить минимальный демо-файл, который сможет стабильно воспроизвести эту ситуацию и протестировать ее на других системах - это сохраняется?только для конкретных версий?если это не может быть воспроизведено на другом компе - нужно отладить на вашем конкретном

...