超高性能套接字服务器-实现细节

3
我已经研究过了,我知道实现高性能套接字服务器的最佳方法通常如下:使用异步套接字操作(专门为获得最佳性能的SocketAsyncEventArgs/Operations),在异步回调上将请求推送到由线程池管理的处理队列中。
对于这个处理模型,我有以下问题,以获得最佳性能:
1)应该在什么时候调用套接字的“End”操作(例如EndAccept或EndReceive)?应该在回调线程(IOCP)中调用它,然后再将请求排队吗?还是在请求被取出队列并被处理(工作线程)时调用?
2)这个问题取决于第一题的答案。什么时候应该调用下一个“Begin”操作?我们应该在调用EndOperation之前甚至在调用EndOperation之后立即调用它(在排队/处理请求之前)?
3)处理队列可以简单地使用.NET线程池吗?使用.NET线程池与自己编写同步处理队列相比,有什么优势/缺点?
任何帮助都将不胜感激。

1
你没有做好研究:SocketAsyncEventArgs和Begin/End是两种不同的模式。当你调用ReceiveAsync时,你不会调用例如EndReceive。 - dtb
关于 #1,EndXXX 操作应该在回调内和外同时调用(如果请求同步完成)。这有点棘手,需要谨慎操作。可以参考这篇博客中的说明。如果你想避免这种情况,使用更优雅的解决方案,我建议你采用 TPL 和它的 FromAsync - Ilian
1
@dtb 你说得对,模型略有不同,但两种模式都遵循通过回调进行异步处理的总体概念,我的问题仍然与两者相关。 - shyneman
1个回答

2
1) 在异步回调函数中,EndReceive 应该是你要做的第一件事情。实际上,在你调用 EndReceive 之前,你在回调函数中几乎不能完成任何其他操作,因为那是提供接收到的数据的方法。可以参考 Socket.EndReceive 的示例。
同样的道理也适用于 EndAccept,因为它会提供你将要通信的套接字。
2) 在调用 EndAccept 后,应尽快调用 BeginAccept。否则,如果你的接受回调函数处理时间太长,就有可能会错过连接请求。当然,如果你的接受回调函数需要很长时间才能完成任何操作,那么这肯定是错误的。同样的道理也适用于 BeginReceive:在调用 EndRead 之后尽快调用它,以避免数据丢失。或者,如果你的通信协议是请求/响应模型,并且客户端在发送更多数据之前需要等待响应,那么你可以等到发送响应后再调用 BeginRead
处理可以使用.NET线程池,但如果您要使用它,应该考虑使用任务并行库(TPL)。使用TPL的优点是非常“点火并忘记”,或者说“点火后它会在完成时回调”。使用TPL的缺点是,应用程序更难以知道哪些任务处于挂起状态。如果您创建自己的同步处理队列,则始终知道待处理的作业,并且有可能检查队列、取消作业等。如果您想对TPL做到这一点,就必须创建一个任务集合来进行管理,因为no way to get the the list of pending tasks

但是,如果您不需要查看待处理的任务,则TPL应该很好地工作。

此处的相关问题提供了一些有用的信息。


关于#1,我想补充一点,标准APM模式要比这复杂一些。因为在简单的实现中堆栈可能会溢出。首先,如果请求同步完成,则应该在回调之外也调用EndXXX方法。这是一个有关此问题的博客。但是,如果使用TPL,则已经解决了这个问题。 - Ilian
对于问题#1:如果您在没有调用Endxxx的情况下排队整个异步结果,那么您不能更早地释放IOCP线程吗?对于问题#2,我认为我们可以达成一致,应该尽快调用它,以便您可以处理接受缓冲区中的下一个连接。对于问题#3,我会研究TPL……感谢所有建议它的人。 - shyneman
@IlianPinzon,那您是说应该在处理工作线程中调用Endxxx方法而不是套接字IOCP线程吗?感谢您建议使用TPL。 - shyneman
@shyneman,我不确定我是否正确理解了您的意思。如果请求未同步完成,则应在回调函数内调用EndXXX方法。如果请求已同步完成,则必须在调用BeginXXX的位置调用EndXXX。我提供的链接有正确的模式。 - Ilian
@IlianPinzon 如果请求处理是CPU密集型的,将请求放入队列中是否值得?这样做不会只增加线程上下文切换而没有额外的输出吗? - shyneman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接