.NET没有可靠的异步套接字通信?

我曾经在.NET中写过一个Crawler。 为了提高其可伸缩性,我尝试利用.NET的异步API。

System.Net.HttpWebRequest具有异步API BeginGetResponse / EndGetResponse。 但是,这对API只是为了获取HTTP响应头和Stream实例,我们可以从中提取HTTP响应内容。 所以,我的策略是使用BeginGetResponse / EndGetResponse来异步获取响应Stream,然后使用BeginRead / EndRead从响应Stream实例异步获取字节。

在Crawler进行压力测试之前,一切看起来都很完美。 在压力测试下,Crawler遭受高内存使用。 我用WinDbg + SoS检查了内存,并说明System.Threading.OverlappedData实例有很多字节数组。 经过一些互联网搜索后,我从微软发现了这个KB http://support.microsoft.com/kb/947862 。

根据KB,异步I / O的数量应该具有“上限”,但它不会告诉“建议的”绑定值。 所以,在我看来,这个KB没有任何帮助。 这显然是一个.NET错误。 最后,我不得不放弃从响应Stream中异步提取字节的想法,并且只是以同步方式进行。

允许带有点网络套接字的异步IO的.NET库(Socket.BeginSend / Socket.BeginReceive / NetworkStream.BeginRead / NetworkStream.BeginWrite)必须具有其异步IO的未完成缓冲区(发送或接收)数量的上限。

网络应用程序应该具有其发布的未完成异步IO数量的上限。

编辑:添加一些问号。

任何人都有在Socket和NetworkStream上进行异步I / O的经验吗? 一般来说,生产中的爬虫是否通过同步或异步的互联网进行I / O操作?

Hmya,这不是.NET框架问题。 链接的知识库文章本来可能更明确一点:“你正在使用一把装满的枪,当你瞄准你的脚时会发生这种情况”。 该枪中的子弹是.NET,使您能够启动尽可能多的异步I / O请求。 它会做你要求它做的事情,直到你遇到某种资源限制。 在这种情况下,可能在第0代堆中有太多固定的接收缓冲区。

资源管理仍然是我们的工作,而不是.NET。 它与没有绑定的内存分配没有什么不同。 解决此特定问题需要限制未完成的BeginGetResponse()请求的数量。 有数百个没有意义,他们每个人都必须一次挤过Intertube。 添加另一个请求只会导致完成时间更长。 或者崩溃你的程序。

您显然希望限制并发请求的数量,无论您的爬虫是同步还是异步。 这个限制是不固定的,这取决于你的硬件,网络,……

我不太确定你的问题是什么,因为HTTP /套接字的.NET实现是“好的”。 有一些漏洞(请参阅我关于正确控制超时的post ),但它完成了工作(我们有一个生产爬虫,每秒可以获取数百页)。

顺便说一句,我们使用同步IO,只是为了方便起见。 每个任务都有一个线程,我们限制并发线程的数量。 对于线程管理,我们使用Microsoft CCR 。

这不仅限于.Net。

这是一个简单的事实,每个异步请求(文件,网络等)都使用内存和(在某些时候,至少是网络请求)非页面缓冲池(有关非托管代码中可能遇到的问题的详细信息,请参阅此处 )。 因此,未完成请求的数量受内存量的限制。 在Vista之前有一些非常低的非页面缓冲池限制会在内存不足之前导致问题,但在后vista环境中,非页面缓冲池使用情况要好得多(参见此处 )。

它在托管代码中要复杂一点,因为除了在非托管世界中遇到的问题之外,还必须处理这样一个事实,即用于异步请求的内存缓冲区会被固定,直到这些请求完成为止。 听起来你在读取方面遇到了这些问题,但是对于写入来说就差一点,如果不是更坏的话(一旦TCP流控制开始连接,那么发送完成将开始花费更长的时间,因此这些缓冲区固定的时间越来越长 – 见这里和这里 )。

问题不在于.Net异步的东西是破坏的,只是抽象是这样的,它使它看起来比实际上更容易。 例如,要避免固定问题,请在程序启动时将所有缓冲区分配到单个大型连续块中,而不是按需分配…

就个人而言,我会在非托管代码中编写这样的爬虫,但那只是我;)你仍然会遇到很多问题,但是你可以对它们有更多的控制权。

没有KB文章可以给你一个上限。 上限可以根据可用的硬件而有所不同 – 对于具有16g内存的机器,2G内存机器的上限将是不同的。 它还取决于GC堆的大小,碎片的碎片程度等。

您应该做的是使用信封计算后面的公制。 计算出每分钟要下载的页数。 这应该确定您想要多少异步请求(N)。

一旦你知道N,就创建一段代码(比如生产者 – 消费者管道的消费者端),它可以创建N个未完成的异步下载请求。 一旦请求完成(由于超时或由于成功),通过从队列中拉出工作项来启动另一个异步请求。

您还需要确保队列不会超出边界,例如,无论出于何种原因,下载都会变慢。

当您使用套接字的异步Send(BeginSend)方法时会发生这种情况。 如果你使用自己的自定义线程池,并通过线程发送数据同步发送方法主要是解决这个问题。 经过测试和certificate。