2014-11-01 5 views
5

У меня есть вопрос относительно вопроса this («Несколько клиентов асинхронного сервера»).Роль асинхронного сервера Microsoft Пример

Либо Microsoft изменил the example так Гроос ответа или я действительно не понимаю - в примере он говорит:

 while (true) { 
      // Set the event to nonsignaled state. 
      allDone.Reset(); 

      // Start an asynchronous socket to listen for connections. 
      Console.WriteLine("Waiting for a connection..."); 
      listener.BeginAccept( 
       new AsyncCallback(AcceptCallback), 
       listener); 

      // Wait until a connection is made before continuing. 
      allDone.WaitOne(); 
     } 

Как я понимаю функцию BeginAccept() называется contiuously в то время (правда,), только останавливаясь и до тех пор, пока не будет вызвана функция AcceptCallback(), потому что первое, что происходит там, - allDone.Set().

Но Гру сказал

Проблема с примером MSDN является то, что он позволяет подключать только одного клиента (listener.BeginAccept вызывается только один раз).

И на самом деле я не понимаю, почему ManualResetEvent allDone используется на всех ... И я думал, что метод listener.EndAccept (аг) блокирует в любом случае.

Является ли listener.BeginAccept() выдающим исключение, если он вызван во второй раз во время работы? Но если да, почему allDone.Set() перед слушателем.EndAccept (ar)?

И еще один вопрос:

Могу ли я просто позвонить handler.BeginReceive (...), в (ар IAsyncResult) ReadCallback функция после того, как я получил EOF, ждать больше Incomming данных того же клиент?

Может ли кто-нибудь с большим опытом объяснить это мне, пожалуйста?

Спасибо!

+1

Прошло некоторое время с тех пор, как я ответил на это, но у IIRC исходный пример не имел цикла. Мы можем проверить [страницу интернет-архива за этот период] (http://web.archive.org/web/20110201000000*/http://msdn.microsoft.com/en-us/library/fx6588te.aspx), но сервер в настоящее время, похоже, не работает. – Groo

+0

, тогда возникает проблема со старым вопросом, так как у него есть ссылка на обновленный исходный код ... – fosb

ответ

7

Возможно, пример был фактически обновлен, так как был отправлен другой ответ 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 :)).

+0

wow, спасибо за ваше время, отвечая на эту деталь @Peter Duniho! Я действительно ценю это! Спасибо за помощь и время. – fosb

0

Образец путают. Мероприятие не требуется. Вместо этого, callback-вызов accept должен выпустить следующую операцию accept, чтобы всегда было принято одно сообщение.

Вызов BeginAccept в нерегулируемом цикле был бы неправильным, так как это запустило неограниченное количество выдающихся операций accept.

Знаете ли вы, что с момента введения await старый APM устарел? Весь этот код сегодня не имеет значения.

Также обратите внимание, что большинство кодов сокетов в Интернете глубоко ошибочны по-разному.

+0

Спасибо за ответ, это привело меня к следующим вопросам: > Знаете ли вы, что с момента появления старый APM устарел? Весь этот код сегодня не имеет значения. – fosb

+0

извините, создал первый ответ случайно ... @usr, но спасибо за ответ, это привело меня к следующим вопросам: * Следующий прием обратного вызова вызывает listener.BeginAccept()? Правильно ли я понял, что тогда listener.EndAccept() ждет следующего входящего соединения? * Считаете ли вы, что использование TCPListener и TCPClient - лучший способ? «Вы знаете, что с момента появления старого APM устарело? Весь этот код сегодня не имеет значения». Вы имеете в виду, что лучше пойти с EAP? Еще раз спасибо ^^ – fosb

+0

Начало и конец спарены. Любой заданный конечный вызов соответствует вызову Begin и получает его результат. Конец просто дает вам соединение, которое уже было принято. Затем снова вызовите Begin, чтобы снова начать прием; TcpListener/Client - тонкие обертки. Они предпочтительнее .; EAP также устарел. Используйте приложение NetworkStream. Используйте методы async IO, такие как ReadAsync. – usr

Смежные вопросы