代码之家  ›  专栏  ›  技术社区  ›  Poni

使用“待发送”队列和其他设计问题限制TCP发送

  •  4
  • Poni  · 技术社区  · 14 年前

    这个问题是我最近几天问的另外两个问题的结果。
    我正在创建一个新问题,因为我认为这与我理解如何控制发送/接收流的“下一步”有关,这是我还没有得到完整答案的问题。
    其他相关问题包括:
    An IOCP documentation interpretation question - buffer ownership ambiguity
    Non-blocking TCP buffer issues

    总之,我使用的是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吗?

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

    我很想看到有人花时间回答这个问题。我保证我会加倍投票给他!(:
    谢谢大家!

    3 回复  |  直到 13 年前
        1
  •  3
  •   Len Holgate    13 年前

    首先:我会单独问这个问题。你更有可能这样得到答案。

    我在我的博客上说了很多: http://www.lenholgate.com 但既然你已经给我发邮件说你读了我的博客,你就知道…

    TCP流控制问题是这样的,因为您要发布异步写操作,而这些操作在完成之前都会使用资源(请参见 here )在写操作挂起期间,需要注意各种资源使用问题,而数据缓冲区的使用是其中最不重要的一个;您还将使用一些非分页池,这是一个有限的资源(尽管Vista中的可用资源比以前的操作系统多得多,但比以前的操作系统晚),您还将锁定页面在写入期间的内存和操作系统可以锁定的总页数是有限制的。请注意,非分页池的使用和页面锁定问题在任何地方都没有很好的记录,但是一旦你点击了enobufs,你就会发现写操作失败。

    由于这些问题,不受控制的等待写入次数是不明智的。如果您发送的数据量很大,并且没有应用程序级的流控制,那么您需要注意,如果发送数据的速度比连接的另一端处理数据的速度快,或者比链接速度快,那么您将开始使用大量的上述资源,因为您的写入操作需要更长的时间才能完成,因为o TCP流控制和窗口问题。当TCP堆栈由于流控制问题而无法再写时,写调用只会阻塞套接字代码,因此您不会遇到这些问题;当异步写操作完成后,写操作将挂起。通过阻塞代码,阻塞处理您的流控制;通过异步写入,您可以继续循环以及越来越多的数据,这些数据都只是等待TCP堆栈发送…

    无论如何,由于这个原因,对于Windows上的异步I/O,您应该始终拥有某种形式的显式流控制。因此,您可以使用ACK将应用程序级流控制添加到您的协议中,这样您就可以知道数据何时到达了另一端,并且在任何时候都只允许有一定数量的数据未完成,或者如果您不能添加到应用程序级协议中,您可以使用写入完成来驱动数据。诀窍是允许每个连接有一定数量的未完成的写操作,并在达到限制后对数据进行排队(或者只是不生成数据)。然后,当每次写入完成时,您可以生成一个新的写入….

    关于汇集数据缓冲区的问题是,imho,现在就过早地进行优化。到达您的系统正常工作的地方,您已经分析了您的系统,发现缓冲池上的争用是最重要的热点,然后解决它。我发现每个线程的缓冲池并不能很好地工作,分配的分布和线程间的空闲并不像工作需要的那样平衡。我在我的博客上说了更多: http://www.lenholgate.com/blog/2010/05/performance-comparisons-for-recent-code-changes.html

    关于部分写入完成的问题(发送100个字节,完成后返回,并说您只发送了95个字节)在实践中并不是真正的问题。如果你到了这个位置并且有不止一篇未完成的文章,那么你就无能为力了,后续的文章可能会很好地工作,你将从你期望发送的内容中丢失字节;但是a)我从未见过这种情况发生,除非你已经遇到了我上面详述的资源问题;b)如果你已经在该连接上发布了更多的写操作,因此只需中止连接—请注意,这就是为什么我总是在运行它们的硬件上配置我的网络系统,并且我倾向于在代码中设置限制,以防止达到OS资源限制(在Vista之前的操作系统中,坏驱动程序通常会在框中显示蓝色,如果没有。嘿,如果你不注意这些细节的话,你就不能得到无页面的游泳池,所以你可以把一个盒子拿下来。

    下次请分开提问。

        2
  •  1
  •   Pavel Radzivilovsky    14 年前

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

    使用完成端口,它将作为“新数据”事件到达。将新数据视为“read ok”,因此还有一个“write ok”事件。API之间的名称不同。

    Q2。如果对每一块数据进行互斥采集的内核模式转换会伤害到您,我建议重新考虑您正在做什么。它最多需要3微秒,而线程调度程序切片在Windows上可能长达60毫秒。

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

        3
  •  1
  •   jer    14 年前

    为了解决您的问题,即它何时知道要减速,您似乎缺乏对TCP拥塞机制的理解。”“慢开始”是你所说的,但它并不完全是你所说的。慢启动就是——从慢开始,然后变快,直到另一端愿意去的速度,线速度,不管什么。

    至于剩下的问题,帕维尔的回答就足够了。