можно ли вообще считать ошибкой печально известную ошибку `ERROR_NETNAME_DELETED'?

Я пишу TCP-сервер в Windows NT, используя порты завершения для использования асинхронного ввода-вывода. У меня есть класс TcpSocket, класс TcpServer и некоторые (виртуальные функции) обратные вызовы для вызова после завершения операции ввода-вывода, например. onRead() для завершения чтения. У меня также есть onOpen(), когда соединение установлено, и onEof(), когда соединение закрыто, и так далее. У меня всегда есть отложенное чтение для сокета, поэтому, если сокет эффективно получает данные (чтение будет завершено с размером > 0), он вместо этого вызывает onRead(), если клиент закрывает сокет со стороны клиента (чтение будет завершено с размером == 0) он вызывает onEof(), и сервер знает, когда клиент закрывает сокет с помощью closesocket(server_socket); со своей стороны.

Все работает изящно, но я заметил одну вещь:

когда я вызываю closesocket(client_socket); на стороне сервера конечной точки соединения, а не на стороне клиента, (либо с настройкой linger {true, 0}, либо нет) отложенное чтение будет завершено как ошибочное, то есть размер чтения будет не только == 0, но также GetLastError() возвращает ошибку: 64 или «ERROR_NETNAME_DELETED». Я много искал об этом в Интернете, но не нашел ничего интересного.

Тогда я спросил себя: а настоящая ли это ошибка? Я имею в виду, можно ли это действительно считать ошибкой?

Проблема в том, что на стороне сервера обратный вызов onError() будет вызываться, когда я closesocket(client_socket); вместо onEof(). Поэтому я подумал так:

Что насчет того, если я при получении этой «ошибки» «ERROR_NETNAME_DELETED» вызову onEof() вместо onError()? Приведет ли это к некоторым ошибкам или неопределенному поведению? Еще один важный момент, который заставил меня задать этот вопрос, заключается в следующем:

Когда я получил это завершение чтения с «ERROR_NETNAME_DELETED», я проверил структуру OVERLAPPED, в частности, параметр перекрытия->Internal, который содержит код ошибки NTSTATUS базового драйвера. Если мы видим список кодов ошибок NTSTATUS [ http://www.tenox.tc/links/ntstatus.html ] мы можем ясно видеть, что «ERROR_NETNAME_DELETED» генерируется NTSTATUS 0xC000013B, что является ошибкой, но называется «STATUS_LOCAL_DISCONNECT». Ну, это не похоже на название ошибки. Это больше похоже на «ERROR_IO_PENDING», что является ошибкой, но также и статусом для правильного поведения.

Итак, как насчет проверки внутреннего параметра структуры OVERLAPPED, и когда он равен == 'STATUS_LOCAL_DISCONNECT', выполняется вызов обратного вызова onEof()? Испортит ли дело?

Кроме того, я должен сказать, что со стороны сервера, если я вызываю DisconnectEx() перед вызовом closesocket(client_socket); Я не получу эту ошибку. Но как насчет того, что я не хочу вызывать DisconnectEx()? Например. когда сервер выключается и не хочет ждать всех завершений DisconnectEx(), а просто хочет закрыть все подключенные клиенты.


person Marco Pagliaricci    schedule 24.01.2013    source источник
comment
@ Ганс, я думаю, он отлично описал, как он столкнулся с этой ошибкой.   -  person Ben Voigt    schedule 25.01.2013


Ответы (3)


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

Другим примером такого рода является случай, когда вы вызываете функцию API, но не знаете, какой размер буфера предоставить. Таким образом, вы предоставляете буфер, который, как вы надеетесь, будет достаточно большим. Но если вызов API завершается неудачей, вы затем проверяете, что последняя ошибка — ERROR_INSUFFICIENT_BUFFER. Это ожидаемое состояние ошибки. Затем вы можете повторить попытку с большим буфером.

person David Heffernan    schedule 24.01.2013
comment
Я согласен. Единственный недостаток, который я могу себе представить, заключается в том, что если эта ошибка ERROR_NETNAME_DELETED генерируется для других вещей в Windows, а затем, когда возникает реальное состояние ошибки, вместо onError() будет вызываться обратный вызов onEof(). Так что, возможно, я могу просто проверить NTSTATUS в структуре OVERLAPPED. - person Marco Pagliaricci; 24.01.2013
comment
Вы не должны читать значение NTSTATUS из структуры OVERLAPPED. Он внутренний и может быть изменен. В документации ясно об этом. - person David Heffernan; 24.01.2013
comment
Да, верно, член Internal не предназначен для использования, так что, возможно, я буду полагаться на GetLastError() - person Marco Pagliaricci; 24.01.2013

Вам решать, как обрабатывать состояние ошибки, но вопрос является признаком потенциальных проблем в вашем коде (от логических ошибок до неопределенного поведения).

Наиболее важным моментом является то, что вы не должны касаться дескриптора SOCKET после closesocket. Что вы делаете в EOF? Было бы логично closesocket на нашей стороне, когда мы обнаружим EOF, но это то, что вы не можете сделать в ERROR_NETNAME_DELETED обработчике, потому что closesocket уже произошло и дескриптор недействителен.

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

Что произойдет, если ожидающее чтение завершится с EOF в этот очень неудачный момент, как раз перед closesocket? Если ваш обычный обратный вызов OnEof запускается, а этот обратный вызов выполняет closesocket, это снова будет неправильно.

Описанная вами проблема может указывать на более серьезную проблему, если closesocket выполняется в одном потоке, а другой поток ожидает завершения ввода-вывода. Вы уверены, что другой поток не вызывает WSARecv/ReadFile, пока первый поток вызывает closesocket? Это неопределенное поведение, даже несмотря на то, что winsock делает вид, что работает большую часть времени.

Подводя итог, код, обрабатывающий завершение (или сбой) чтения, не может быть правильным, если он не знает, что дескриптор сокета бесполезен, поскольку он был закрыт. После closesocket полезно дождаться ожидающего завершения ввода-вывода, потому что вы не можете повторно использовать структуру OVERLAPPED, если вы этого не сделаете; но нет смысла обрабатывать этот тип завершения, как если бы это произошло во время нормальной работы, когда сокет все еще открыт (код ошибки/состояния не имеет значения).

person Anton Kovalenko    schedule 24.01.2013
comment
У тебя действительно хорошая мысль. Ну, в основном у меня есть onEof() для запуска чего-то для очистки (например, освобождения памяти и т. д.), но у меня должно быть два обратных вызова для этого: onEof(), который вызывается только тогда, когда другая сторона закрывает соединение, и onClose () -- таким образом, когда получен onEof(), другая часть может вызвать closesocket(), как вы предложили. Итак, если я понял, если ожидающее чтение завершается до closesocket() и приложение обнаруживает их после, есть ERROR_NETNAME_DELETED, чтобы сигнализировать об этом сценарии, и это его цель? - person Marco Pagliaricci; 24.01.2013
comment
Нет, завершается до / обнаруживается после того, как в сценарии нет ERROR_NETNAME_DELETED, это пример, когда что-то может пойти логически неправильно необнаруженным образом. Это также иллюстрация того, почему, вероятно, неправильно выполнять очистку в обратном вызове ERROR_NETNAME_DELETED: с таким неудачным временем для успешного чтения нечего обрабатывать (и будет обрабатывать нечего, потому что вы не можете перепланировать WSARecv на закрытой розетке). - person Anton Kovalenko; 24.01.2013

Вы вызываете неправильный метод. Вы должны вызывать WSAGetLastError(). Результат GetLastError() после вызова Winsock API не имеет смысла.

person user207421    schedule 24.01.2013
comment
На самом деле я вызываю WSAGetLastError(). А также при этом значение ошибки такое же. Я упомянул GetLastError(), потому что я использую порты завершения ввода-вывода также для несетевого ввода-вывода, но я думаю, что эта ошибка имеет смысл только для сетевого ввода-вывода. - person Marco Pagliaricci; 25.01.2013
comment
@MarcoPagliaricci Так что на самом деле ваш вопрос должен говорить об этом. - person user207421; 28.01.2013