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

fos*_*ose 5 c# sockets asynchronous tcp server

我有一个关于这个问题的问题("异步服务器套接字多个客户端").

要么微软改变了这个例子,因为Groos回答或者我真的没有得到它 - 在示例中它说:

        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();
        }
Run Code Online (Sandbox Code Playgroud)

据我所知,在while(true)循环中连续调用BeginAccept()函数,只有在调用AcceptCallback()函数时才会被调用,因为首先发生的是allDone.Set().

但格罗说

MSDN示例的问题在于它只允许连接单个客户端(listener.BeginAccept只调用一次).

实际上我不明白为什么使用ManualResetEvent allDone ...而且我认为listener.EndAccept(ar)方法无论如何都是阻塞的.

listener.BeginAccept()是否在仍然运行时第二次调用时抛出异常?但是,如果是这样,为什么allDone.Set()在listener.EndAccept(ar)之前呢?

还有一个问题:

在收到EOF后,我可以在ReadCallback(IAsyncResult ar)函数中调用handler.BeginReceive(...)来等待来自同一客户端的更多数据吗?

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

谢谢 !

Pet*_*iho 8

这个例子实际上可能已更新,因为其他SO答案已经发布.或者回答者Groo可能根本就没有完全理解他自己的例子.在任何一种情况下,您都可以正确地观察到他只能接受一个客户的声明是不正确的.

我同意usr写的一些内容,但对整个事情有一些不同的看法.此外,我认为这些问题值得更全面和具体的处理.

首先,虽然我同意优先设计通常是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)......如果没有,它应该只使用SocketShutdown.Receive和/或根本不调用Shutdown直到它完成发送它可以使用SocketShutdown.Both... SocketShutdown.Receive不实际上对连接本身做了很多重要的事情,所以等到SocketShutdown.Both合适时才合理.

换句话说,即使服务器确定客户端不会发送任何其他数据,正确使用套接字API仍然是尝试另一个接收操作,寻找指示客户端的0字节返回值事实上已经开始关闭连接.只有在那时,服务器才能开始自己的关闭过程并关闭套接字.

最后,你没有问,但因为usr提出了这个问题:我不同意这个MSDN示例今天没有相关性.不幸的是,Microsoft没有为Socket该类提供基于任务的异步API版本.还有支持异步其它网络的API/AWAIT(如TcpClient/ NetworkStream),但如果你想使用Socket直接类,你坚持老款异步(Socket有两个,这两个回调为主).

您可以将同步Socket方法包装Tasks为旧API的替代方法,但是您将失去Socket类中基于I/O完成端口的异步API的优势.更好的是某种任务兼容的包装器仍然使用下面的异步API,但实现起来有点复杂,而且我现在还不知道这样的事情(但它当然可能存在,所以可能是如果您更喜欢使用async/await,那么值得您进行一些网络搜索.

希望有所帮助!我为这个长期答案道歉,但这是一个相当广泛的问题(几乎过于宽泛,但对我来说似乎仍然在合理的SO范围内:)).