微软异步服务器套接字示例

时间:2014-11-01 17:58:11

标签: c# sockets asynchronous tcp server

我对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(...)来等待来自同一客户端的更多数据?

任何有经验的人都可以向我解释一下吗?

谢谢!

2 个答案:

答案 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已经过时了?所有这些代码今天都无关紧要。

另外,请注意网络上的大多数套接字代码都有不同的方式。