Возможно, пример был фактически обновлен, так как был отправлен другой ответ SO. Или, возможно, ответчик Гроо просто не полностью понял пример сам. В любом случае вы правильно заметили, что его заявление о том, что только один клиент может быть принято, неверно.
Я согласен с некоторыми из того, что написал usr, но имеет несколько иное отношение к делу. Кроме того, я думаю, что вопросы заслуживают более полного и конкретного лечения.
Во-первых, хотя я согласен с тем, что лучше всего производить последующие вызовы BeginAccept()
в методе обратного вызова accept, а не в цикле, нет ничего неправильного с реализацией в примере. Никакой новый вызов BeginAccept()
не производится до тех пор, пока не будет сообщено о завершении предыдущего вызова; дескриптор события используется для синхронизации потока, в котором вызывается BeginAccept()
с той ветвью, которая обрабатывает завершение. Первый поток освобождается только тогда, когда ранее выполненный accept завершается, а затем только один новый вызов BeginAccept()
выполняется до того, как этот поток блокируется снова.
Это немного неудобно, но полностью кошерный. Возможно, автор этого образца понял, что, поскольку в его консольной программе он все равно будет сидеть там без дела, он мог бы также дать ему что-то делать. :)
В любом случае, чтобы ответить на вопрос № 1: вы правы, пример, присутствующий на этой ссылке, позволяет нескольким клиентам подключаться.
Вопрос №2, почему используется дескриптор события, я надеюсь, что приведенное выше объяснение ответило на это. Это пример, который используется для освобождения потока, который вызывает BeginAccept()
, так что он может позвонить ему в другой раз после завершения предыдущего вызова.
Вопрос № 3, EndAccept()
блокирует? Вроде. Если вы вызываете EndAccept()
до того, как операция accept фактически завершена, тогда да. Он будет блокироваться. Но в этом случае он вызывается только тогда, когда вызван обратный вызов завершения. На этом этапе мы можем быть уверены, что вызов EndAccept()
будет не блок. Все, что он собирается сделать, это получить результат завершенной операции в этот момент (при условии, конечно, никаких исключений).
Вопрос №4, является ли законным звонить BeginAccept()
во второй раз, до того, как был вызван EndAccept()
? Да, даже если не было законно иметь несколько операций принятия операций в очереди (что это такое). Здесь вызов BeginAccept()
происходит в обратном вызове завершения для первого BeginAccept()
. То есть, хотя код еще не вызывал EndAccept()
, сама операция accept имеет, и поэтому даже не имеет случая, когда несколько операций приема не выдаются. Операции приема и отправки аналогичны либеральным; вы можете юридически вызывать все эти методы несколько раз до завершения для любого из них.
Вопрос № 5, можно ли позвонить BeginReceive()
, хотя я получил <EOF>
? Да. Фактически, это область, в которой пример MSDN имеет значение, поскольку он не продолжает получать, как только будет получен последний ожидаемый результат. На самом деле, до тех пор, пока прием не завершится с 0 байтами, он все равно должен всегда снова звонить BeginReceive()
, ожидаются ли или нет данные, а затем обрабатывать завершенный прием, где количество байтов равно 0, вызывая Shutdown(SocketShutdown.Both)
в этой точке для подтверждения подтверждения изящного закрытие соединения (при условии, что он был отправлен по этой точке, и в это время он уже вызвал бы Shutdown(SocketShutdown.Send)
... если нет, он должен просто использовать SocketShutdown.Receive
и/или просто не вызывать Shutdown вообще до тех пор, пока он не будет отправлен, и он может использовать SocketShutdown.Both
... SocketShutdown.Receive
фактически не делает ничего существенного для самого соединения, поэтому разумно просто подождать, пока не будет SocketShutdown.Both
).
Другими словами, даже если сервер знает наверняка клиент не собирается отправлять какие-либо дополнительные данные, правильное использование сокета API - это попытка повторить операцию получения, ища этот 0-байтовый возврат значение, указывающее, что клиент фактически начал отключать соединение. Только в этот момент сервер начнет свой собственный процесс останова и закроет сокет.
И, наконец, вы не спрашивали, а потому, что usr поднял его: я не согласен с тем, что этот пример MSDN сегодня не имеет значения. К сожалению, Microsoft не предоставила Асинхронный API на основе задач для класса Socket
. Там - это другие сетевые API, поддерживающие async/await (например, TcpClient
/NetworkStream
), но если вы хотите напрямую использовать класс Socket
, вы застряли со старыми асинхронными моделями (Socket
имеет два, оба на основе обратного вызова).
Вы можете обернуть синхронные методы Socket
в Tasks
в качестве альтернативы устаревшему API, но тогда вы потеряете преимущество асинхронного API-интерфейса завершения ввода-вывода в классе Socket. Гораздо лучше будет какая-то совместимая с задачами оболочка, которая по-прежнему использует асинхронный API под ним, но это несколько сложнее реализовать, и я не знаю о такой вещи в данный момент (но она, безусловно, может существовать, стоит немного веб-поиска с вашей стороны, если вы предпочитаете использовать async/wait).
Надеюсь, что это поможет! Я извиняюсь за длинный ответ, но это был довольно широкий вопрос (почти слишком широкий, но для меня он казался все еще в пределах разумных границ SO :)).
Прошло некоторое время с тех пор, как я ответил на это, но у IIRC исходный пример не имел цикла. Мы можем проверить [страницу интернет-архива за этот период] (http://web.archive.org/web/20110201000000*/http://msdn.microsoft.com/en-us/library/fx6588te.aspx), но сервер в настоящее время, похоже, не работает. – Groo
, тогда возникает проблема со старым вопросом, так как у него есть ссылка на обновленный исходный код ... – fosb