我对this问题(“异步服务器套接字多个客户端”)有疑问。
自从Groos回答或我真的没有得到它后,微软改变了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();
}
据我所知,在while(true)循环中连续调用BeginAccept()函数,只有在调用AcceptCallback()函数时才会被调用,因为首先发生的是allDone.Set()。
但Groo说
MSDN示例的问题是它只允许连接一个客户端(listener.BeginAccept只调用一次)。
实际上我不明白为什么一直使用ManualResetEvent allDone ... 而且我认为listener.EndAccept(ar)方法无论如何都会阻塞。
如果仍然在运行时第二次调用,是listener.BeginAccept()抛出异常吗? 但是,如果是这样,为什么allDone.Set()在listener.EndAccept(ar)之前?
另一个问题:
在收到 EOF 之后,我可以在ReadCallback(IAsyncResult ar)函数中调用handler.BeginReceive(...)来等待来自同一客户端的更多数据?
任何有经验的人都可以向我解释一下吗?
谢谢!
答案 0 :(得分:8)
可能的例子实际上已更新,因为其他SO答案已发布。或者,回答者Groo根本没有完全理解他自己的例子。在任何一种情况下,你都可以观察到他只能接受一个客户的声明是不正确的。
我同意我们写的一些内容,但对整个事情有一些不同的看法。此外,我认为这些问题值得更全面和具体的处理。
首先,虽然我同意在接受回调方法中发出对BeginAccept()
的后续调用而不是使用循环通常是优越的设计,但在示例中的实现本身并没有错。在发出上一次通话结束后,才会对BeginAccept()
进行新的通话;事件句柄用于同步调用BeginAccept()
的线程与处理完成的任何线程。第一个线程仅在先前发出的接受完成时释放,然后在该线程再次阻塞之前仅对BeginAccept()
进行一次新调用。
有点尴尬,但完全是犹太洁食。这个样本的作者可能认为,因为在他的控制台程序中,无论如何他都会在那里闲置一个线程,他不妨给它一些事情要做。 :)
无论如何,回答问题#1:你是对的,当前在该链接上的例子确实允许多个客户端连接。
问题#2,为什么使用事件句柄,我希望上面的解释已经回答了。它是用于释放调用BeginAccept()
的线程的示例,以便在前一次调用完成后可以再次调用它。
问题#3,EndAccept()
正在阻止?有点。如果在接受操作实际完成之前调用EndAccept()
,则为是。它会阻止。但在这种情况下,只有在调用完成回调时才会调用它。此时,我们可以确定对EndAccept()
的调用将不阻止。它所要做的就是在那时检索已完成操作的结果(当然,假设没有例外)。
问题#4,在调用BeginAccept()
之前第二次致电EndAccept()
是否合法?是的,即使将多个接受操作排队(这是合法的)也是不合法的。在此,BeginAccept()
的调用发生在第一个BeginAccept()
的完成回调中。也就是说,虽然代码尚未调用EndAccept()
,但接受操作本身已经完成,因此甚至不会出现多个接受操作未完成的情况。接收和发送操作同样自由;你可以在任何事情发生完成之前多次合法地调用所有这些方法。
问题#5,即使我已收到BeginReceive()
,我也可以致电<EOF>
吗?是。实际上,这是MSDN示例 缺陷的区域,因为一旦接收到最后一个预期数据,不继续接收。实际上,在接收以0字节完成之前,它仍应始终再次调用BeginReceive()
,无论是否需要更多数据,然后通过调用Shutdown(SocketShutdown.Both)
来处理字节计数为0的已完成接收。这点表示确认连接的正常关闭(假设它已完成发送,此时它已经调用Shutdown(SocketShutdown.Send)
...如果没有,它应该只使用{{ 1}}和/或根本不调用Shutdown直到它完成发送,它可以使用SocketShutdown.Receive
... SocketShutdown.Both
实际上并没有对连接本身做任何重要事情,所以等到SocketShutdown.Receive
合适是合理的。)
换句话说,即使服务器确定,客户端也不会发送任何其他数据,正确使用套接字API仍然是尝试另一个接收操作,对于该0字节的返回值,表示客户端实际上已开始关闭连接。只有在那时,服务器才能开始自己的关机过程并关闭套接字。
最后,你没有问,但因为我们提出了这个问题:我不同意这个MSDN示例今天没有关系。遗憾的是,Microsoft没有为SocketShutdown.Both
类提供基于任务的异步API版本。 支持异步/等待的其他网络API(例如Socket
/ TcpClient
),但是如果您想直接使用NetworkStream
类,那么你就是#39 ;坚持旧的异步模型(Socket
有两个,都基于回调)。
您可以将Socket
中的同步Socket
方法包装为旧API的替代方法,但是您将失去基于I / O完成端口的异步API的优势。 Socket类。更好的是某种任务兼容的包装器仍然使用下面的异步API,但实现起来有点复杂,而且我现在还没有意识到这样的事情(但它当然可以存在,所以如果您更喜欢使用async / await,那么您可能需要进行一些网络搜索。
希望有所帮助!我为这个长期答案道歉,但这是一个相当广泛的问题(几乎过于宽泛,但对我来说似乎仍然在合理的SO范围内:))。
答案 1 :(得分:0)
样本很混乱。不需要这个活动。相反,接受回调应该发出下一个接受操作,以便始终有一个接受未完成。
在非节流循环中调用BeginAccept是不正确的,因为这将启动无限数量的未完成接受操作。
您是否意识到自await
引入以来,旧的APM已经过时了?所有这些代码今天都无关紧要。
另外,请注意网络上的大多数套接字代码都有不同的方式。