Почему windows select () не всегда уведомляет select () потока B, когда поток A закрывает свой конец пары сокетов? - PullRequest
6 голосов
/ 28 января 2010

Ситуация, возникающая у меня под Windows XP (SP3), сводит меня с ума, и я достигаю конца своей привязи, так что, возможно, кто-то может дать некоторое вдохновение.

У меня есть сетевая программа на C ++ (без GUI). Эта программа предназначена для компиляции и запуска под Windows, MacOS / X и Linux, поэтому она использует select () и неблокирующий ввод / вывод в качестве основы для своего цикла обработки событий.

В дополнение к своим сетевым обязанностям, эта программа должна читать текстовые команды из stdin и корректно завершать работу, когда stdin закрывается. В Linux и MacOS / X это достаточно просто - я просто включаю STDIN_FILENO в свое чтение fd_set для select (), и select () возвращает, когда stdin закрывается. Я проверяю, что FD_ISSET (STDIN_FILENO, & readSet) имеет значение true, пытаюсь прочитать некоторые данные из stdin, recv () возвращает 0 / EOF, и поэтому я выхожу из процесса.

С другой стороны, в Windows вы не можете выбирать в STDIN_FILE_HANDLE, потому что это не настоящий сокет. Вы также не можете выполнять неблокирующие чтения на STDIN_FILE_HANDLE. Это означает, что нет способа читать stdin из основного потока, так как ReadFile () может блокироваться бесконечно, в результате чего основной поток перестает обслуживать свою сетевую функцию.

Нет проблем, говорит я, я просто создаю поток, чтобы обрабатывать stdin для меня. Этот поток будет выполняться в бесконечном цикле, блокируясь в ReadFile (stdinHandle), и всякий раз, когда ReadFile () возвращает данные, поток stdin записывает эти данные в сокет TCP. Другой конец этого сокетного соединения будет выбран select () основным потоком, поэтому основной поток будет видеть данные stdin, поступающие через соединение, и обрабатывать «stdin» так же, как и в любой другой ОС. И если ReadFile () возвращает false, чтобы указать, что stdin закрылся, поток stdin просто закрывает свой конец пары сокетов, так что основной поток будет уведомлен через select (), как описано выше.

Конечно, в Windows нет хорошей функции socketpair (), поэтому мне пришлось свернуть свою собственную, используя listen (), connect () и accept () (как видно из функции CreateConnectedSocketPair () здесь . Но я так и сделал, и, похоже, в целом работает.

Проблема в том, что он не работает на 100%. В частности, если stdin закрывается в течение нескольких сотен миллисекунд после запуска программы, примерно в половине случаев основной поток не получает никакого уведомления о том, что конец stdin пары сокетов был закрыт. Под этим я подразумеваю то, что я вижу (по моей отладке printf), что поток stdin вызвал closesocket () для своего сокета, и я вижу, что основным потоком является select () на связанном сокет (т.е. другой конец пары сокетов), но select () никогда не возвращается так, как должен ... и, если он действительно возвращается, из-за того, что какой-то другой сокет выбрал готовый к чему угодно, FD_ISSET (main_thread_socket_for_socket_pair, & readSet) возвращает 0, как будто соединение не было закрыто.

На данный момент единственная гипотеза, которую я имею, заключается в том, что в реализации Windows select () есть ошибка, из-за которой метод select () основного потока не замечает, что другой конец пары сокетов закрылся stdin. -нить. Есть ли другое объяснение? (Обратите внимание, что об этой проблеме сообщалось и в Windows 7, хотя я лично не рассматривал ее на этой платформе)

Ответы [ 6 ]

1 голос
/ 10 февраля 2010

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

Приношу извинения всем за то, что вы потратили время на красную сельдь; если есть стандартный способ закрыть это дело как необоснованное, дайте мне знать, и я сделаю это.

-Jeremy

0 голосов
/ 06 февраля 2010

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

Вы сказали: «Вы не можете выполнять неблокирующее чтение на STDIN_FILE_HANDLE. Это также означает, что нет способа читать stdin из основного потока, поскольку ReadFile () может блокировать бесконечно», но это не совсем история. Посмотрите на ReadConsoleInput, WSAEventSelect и WaitForMultipleObjects. Описатель stdin будет сигнализироваться только тогда, когда есть ввод, и ReadConsoleInput немедленно вернется (почти такая же идея, что и select () в Unix).

Или используйте ReadFileEx и WaitForMultipleObjectsEx, чтобы консоль считывала запуск APC (который не является асинхронным, работает в основном потоке и только во время WaitForMultipleObjectsEx или другой явной функции ожидания).

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

Или, может быть, ваша проблема в другом. Документы select говорят: «Для сокетов, ориентированных на соединение, удобочитаемость также может указывать на то, что запрос на закрытие сокета был получен от однорангового узла». Обычно вы отправляете этот «запрос на закрытие сокета», вызывая shutdown (), а не closesocket ().

0 голосов
/ 05 февраля 2010

Честно говоря, ваш код слишком длинный, и сейчас у меня нет времени на него тратить.

Скорее всего, проблема в том, что в некоторых случаях закрытие сокета не приводит к корректному (FIN) отключению.

Проверка исключений, возвращаемых из вашего выбора, может охватить остальные случаи. Существует также (небольшая) вероятность того, что в сокет фактически не отправляется уведомление о том, что другой конец закрыт. В этом случае нет другого способа, кроме тайм-аутов или сообщений «поддерживать активность» / ping между конечными точками, узнать, что сокет закрыт.

Если вы хотите точно выяснить, что происходит, отключите Wireshark и найдите FIN и RST (и отсутствие чего-либо). Если вы видите правильную последовательность FIN, проходящую при закрытом сокете, проблема должна быть в вашем коде. если вы видите RST, он может быть пойман исключениями, и если вы ничего не видите, вам потребуется разработать в протоколе способ «пинговать» каждую сторону соединения, чтобы убедиться, что они все еще живы, или установить достаточно короткий тайм-аут для получения дополнительных данных.

0 голосов
/ 05 февраля 2010

Не решение, но в качестве обходного пути, не могли бы вы отправить какое-то волшебное сообщение "stdin is closed" через сокет TCP и заставить принимающую сторону отсоединить свой сокет, когда он это увидит, и запустить любой обработчик "stdin closed"

0 голосов
/ 28 января 2010

Я учусь на вашем коде. В CreateConnectedSocketPair () socket1 используется для listen (), а newfd используется для отправки / получения данных. Итак, почему "socket1 = newfd"? Как закрыть listenfd тогда?

0 голосов
/ 28 января 2010

Возможно ли у вас состояние гонки? Например. Вы гарантируете, что функция CreateConnectedSocketPair() обязательно вернулась, прежде чем stdin-thread сможет попытаться закрыть свой сокет?

...