4

这个问题是我在过去几天提出的另外两个问题的结果。
我正在创建一个新问题,因为我认为它与我理解如何控制发送/接收流程的“下一步”有关,我还没有得到完整的答案。
其他相关问题是:
IOCP 文档解释问题 - 缓冲区所有权模糊性
非阻塞 TCP 缓冲区问题

总之,我使用的是 Windows I/O 完成端口。
我有几个线程处理来自完成端口的通知。
我相信这个问题与平台无关,并且会得到与在 *nix、*BSD、Solaris 系统上做同样的事情相同的答案。

所以,我需要有自己的流量控制系统。美好的。
所以我发送发送和发送,很多。我怎么知道什么时候开始排队发送,因为接收方限制为 X 数量?

让我们举个例子(最接近我的问题):FTP 协议。
我有两台服务器;一个在 100Mb 链路上,另一个在 10Mb 链路上。
我命令 100Mb 的一个向另一个(10Mb 链接的)发送一个 1GB 的文件。它以 1.25MB/s 的平均传输速率结束。
发送者(100Mb 链接的)如何知道何时停止发送,所以较慢的发送者不会被淹没?(在这种情况下,“待发送”队列是硬盘上的实际文件)。

另一种问这个问题的方法:
我可以从远程收到“保留发送”通知吗?它是内置在 TCP 中还是所谓的“可靠网络协议”需要我这样做?

我当然可以将我的发送限制为固定数量的字节,但这对我来说听起来不合适。

同样,我有一个循环,其中有许多发送到远程服务器,并且在某些时候,在该循环中,我必须确定是否应该对该发送进行排队,或者我可以将其传递给传输层 (TCP)
我怎么做?你会怎么做?当然,当我从 IOCP 收到发送已完成的完成通知时,我将发出其他待处理的发送,这很清楚。

与此相关的另一个设计问题:
由于我要使用带有发送队列的自定义缓冲区,并且当“send-done”通知到达时,这些缓冲区被释放以供重用(因此不使用“delete”关键字) ,我将不得不在该缓冲池上使用互斥。
使用互斥锁会减慢速度,所以我一直在想;为什么不让每个线程都有自己的缓冲池,因此访问它,至少在获取发送操作所需的缓冲区时,不需要互斥体,因为它只属于那个线程。
缓冲池位于线程本地存储 (TLS) 级别。
没有相互池意味着不需要锁,意味着更快的操作,但也意味着应用程序使用更多的内存,因为即使一个线程已经分配了 1000 个缓冲区,另一个正在发送并且需要 1000 个缓冲区来发送内容的线程也需要分配这些都是自己的。

另一个问题:
假设我在“待发送”队列中有缓冲区 A、B、C。
然后我收到一个完成通知,告诉我接收器收到了 15 个字节中的 10 个。我应该从缓冲区的相对偏移量重新发送,还是 TCP 会为我处理它,即完成发送?如果我应该这样做,我是否可以确保这个缓冲区是队列中的“下一个发送”缓冲区,或者它可以是缓冲区 B 吗?

这是一个很长的问题,我希望没有人受伤(:

我希望看到有人花时间在这里回答。我保证我会双重投票给他!(:
谢谢大家!

4

3 回答 3

3

首先:我会将此作为单独的问题提出。你更有可能以这种方式得到答案。

我已经在我的博客上谈到了大部分内容:http: //www.lenholgate.com但是既然你已经给我发电子邮件说你读过我的博客,你就知道......

TCP 流控制问题是这样的,因为您正在发布异步写入,并且这些都使用资源直到它们完成(请参阅此处)。在写入挂起期间,需要注意各种资源使用问题,数据缓冲区的使用是其中最不重要的;您还将使用一些有限资源的非分页池(尽管在 Vista 中可用的资源比以前的操作系统要多得多),您还将在写入期间锁定内存中的页面,并且有操作系统可以锁定的页面总数的限制。请注意,非分页池的使用和页面锁定问题在任何地方都没有得到很好的记录,但是一旦遇到 ENOBUFS,您就会开始看到写入失败。

由于这些问题,让未控制的写入数量不受控制是不明智的。如果您正在发送大量数据并且您没有应用程序级别的流控制,那么您需要注意,如果您发送的数据比连接另一端可以处理的速度快,或者比链接速度快,那么您将开始使用大量上述资源,因为由于 TCP 流控制和窗口问题,您的写入需要更长的时间才能完成。阻塞套接字代码不会出现这些问题,因为当 TCP 堆栈由于流控制问题而无法再写入时,写入调用只会阻塞;with async 写入完成,然后处于挂起状态。使用阻塞代码,阻塞会为您处理流量控制;

无论如何,正因为如此,在 Windows 上使用异步 I/O,你应该总是有某种形式的显式流控制。因此,您可以使用 ACK 将应用程序级别的流控制添加到您的协议中,这样您就可以知道数据何时到达另一端,并且在任何时候只允许一定数量的未完成,或者如果您无法添加到应用程序级协议,您可以使用写入完成来驱动事物。诀窍是允许每个连接有一定数量的未完成写入完成,并在达到限制后将数据排队(或不生成它)。然后随着每次写入完成,您可以生成一个新的写入....

您关于合并数据缓冲区的问题是,恕我直言,您现在过早地进行了优化。到达您的系统正常工作的地步,您已经对系统进行了概要分析,发现缓冲池上的争用是最重要的热点,然后解决它。我发现每个线程的缓冲池运行得不是很好,因为跨线程的分配和释放往往不像你需要的那样平衡。我在我的博客上谈到了更多:http: //www.lenholgate.com/blog/2010/05/performance-comparisons-for-recent-code-changes.html

您关于部分写入完成的问题(您发送 100 个字节并且完成返回并说您只发送了 95 个字节)实际上在恕我直言中并不是真正的问题。如果您到达这个位置并且有多个未完成的写入,那么您无能为力,随后的写入可能会很好地工作,并且您会丢失预期发送的字节;但是a)我从未见过这种情况发生,除非您已经遇到了我上面详述的资源问题和b)那里'

下次请分开提问。

于 2010-06-14T10:38:54.393 回答
1

Q1。大多数 API 会在您最后一次写入并且再次写入可用之后为您提供“可以写入”事件(如果您未能用最后一次发送填充发送缓冲区的主要部分,可能会立即发生)。

使用完成端口,它将像“新数据”事件一样到达。将新数据视为“读取成功”,因此还有一个“写入成功”事件。API 之间的名称不同。

Q2。如果每个数据块的互斥体获取的内核模式转换对您造成伤害,我建议您重新考虑您在做什么。它最多需要 3 微秒,而您的线程调度程序切片在 Windows 上可能高达 60 毫秒。

在极端情况下可能会受到伤害。如果你认为你正在编程极端通信,请再问一次,我保证会告诉你一切。

于 2010-06-13T22:00:04.967 回答
1

为了解决您关于何时知道减速的问题,您似乎缺乏对 TCP 拥塞机制的了解。“慢启动”是您所说的,但并不是您所说的那样。慢速启动就是这样——从慢速开始,然后变得更快,直到另一端愿意去的速度,电线速度等等。

关于您的其余问题,Pavel 的回答应该足够了。

于 2010-06-13T22:02:28.243 回答