为啥我不能激发 TCP 将 send() 拆分为多个 recv()
Posted
技术标签:
【中文标题】为啥我不能激发 TCP 将 send() 拆分为多个 recv()【英文标题】:Why cant I provoke TCP into splitting send() into multiple recv()为什么我不能激发 TCP 将 send() 拆分为多个 recv() 【发布时间】:2021-03-15 21:53:17 【问题描述】:我对套接字的长期理解是不能依赖对 recv() 的调用来返回请求的数据量(无论是否阻塞套接字)
我试图通过在一次呼叫中发送大量 (1mb) 数据来向同事证明这一点,以便发送(从我家,通过有效的 *** 网络到他家 - 涉及多个路由器 wifi 等)
套接字被阻塞了,所以我不希望 send() 返回任何内容,只返回 1mb 并一次性完成,但我确实希望 recv() 调用返回小于 1mb,即使套接字阻塞。
这个测试的背景是说服他我们需要在消息协议中的长度和有效负载,以便您知道消息从哪里开始/结束,并且您不能依赖一个 recv() 调用返回一条消息。如果我们确实有这个协议,为了向他展示一个简单的 recv() 甚至是不够的,我们需要在一个循环中进行 recv(),即使是 4 字节的消息长度字段,以防 recv() 不返回请求的大小。
我对 TCP 通信的理解有误吗?这些年来我是不是一直在做这件事?如果不是,我如何强制这些 recv() 碎片化?
【问题讨论】:
我很惊讶您一次收到所有 1MB。这可能是在从recv()
调用返回之前缓冲了数百个数据包。尝试使用 10MB,甚至 100MB。
这就是我的想法!,1mb,没有拆分,不太可能。我开始怀疑它是否是 *** 软件,因此它与我们之间有多少种网络无关,*** S/W 是否有效地允许在另一端重构无限大小的 TCP 数据包?也许我在家里尝试通过 wifi 连接两台笔记本电脑?
对,所以我做了一个 noddy 应用程序,每秒发送 16k 次心跳,并且完全通过家里的 wifi 完成。走到房子外面,HB 停止了,我没有一次收到部分 recv(),它暂停了,但是当我回到范围内时继续
所以我没有提到,我们使用 SSL 进行连接,但是我们在 SSL 和非 SSL 上都做了很多测试。我上面的 noddy 16k 测试是在 SSL 上完成的(C# SslStream 包装 TCPClient.GetStream)。如果我的阅读器在发出阅读之前等待 5 秒链接,并且我确保我的发件人在那段时间内发送 2 条不同的消息,接收者将获得 2 次阅读!!!!!!但是,如果我切换到非 ssl(使用 Client.GetStream 或 Client.Client),那么阅读器会在 1 次读取中获得两条消息。我将在非 ssl 上重复户外步行测试
走到wifi连接的边缘我不能强制recv在非ssl套接字上进行部分读取。我是不是把头靠在墙上试图“强迫”这种分裂状态?
【参考方案1】:
说服他,我们需要消息协议中的长度和有效负载,以便您知道消息从哪里开始/结束
您肯定需要这样做,如果您需要说服您的同事这样做,请他们解释为什么不这样做。一个 send() 不对应一个 recv(),句号。当然,它可能在某些条件下发生。现在互联网速度很快,默认启用 Nagle。
尝试相反,发送 1000 条小消息,让他们从一个或多个 recv() 调用的结果中剖析单独的消息;他们不能没有适当的消息框架。
【讨论】:
我确实打破了他们的立场,一次发送两个有效负载,两次发送两个一半,他们拒绝接受如果我没有恶意这样做,那么它会起作用(即 TCP 会确保他们的 1 recv() 会收到一条消息)。 另外,我觉得实际上向他们展示分裂的发生将证明毫无疑问,就他们而言,如果它没有发生,它就不存在 另外,我们最终确实得到了协议中的长度,我使用它,但他没有,他只是发出一个 recv() 并说如果我发送一条消息他会收到一条消息,他不读取长度,然后读取该长度的有效载荷 您的协议是否提供打接收方的能力? 点是; tcp 保证按顺序传递数据,而不是消息。 Berkeley 套接字记录为对recv()
的调用,返回接收缓冲区中的内容。到目前为止,整个消息都在该缓冲区中,这对您的同事有好处,但这无论如何都不能保证。一个网络中断,一个丢失并重新传输的数据包,一个操作系统决定返回缓冲区中的一半消息现在,它们的代码将中断。当他们以他们认为有效的方式解释文档时,是的,文档提到了消息——但那是 UDP,而不是 TCP。 TCP 不说消息,它是流式传输的。【参考方案2】:
你的同事大错特错。您根本无法从无法想到或观察到某事可能出错的方式得到保证。要么保证接收将对应于发送,要么没有。
观察在这里没有帮助。
如果您没有观察到与发送不对应的接收,则可能是您观察到的实现中的错误。
如果您确实观察到与发送相对应的接收,这可能是因为您正在测试的实现存在异常情况,并且下一版本的操作系统、编译器、标准库等可能不遵循假设。
您的同事要么依赖有保证的行为,要么不依赖。答案是它们不是。
90 年代末的真实故事:我曾经不得不调试一些“总是工作得很好”的代码,因为它预计 TCP 连接的前 12 个字节会粘在一起。当发现涉及使用 8 个特制恶意字节的攻击时,它就崩溃了,并且在客户位置使用的防御是一个过滤器,该过滤器在将所有 TCP 连接的前 8 个字节传递给应用程序之前拦截并检查它们。结果,应用程序总是在第一次调用recv
时读取前 8 个字节(来自过滤器),违反了(破碎的,愚蠢的)假设,即在 TCP 连接开始时读取 12 个字节总是会得到全部 12 个。
【讨论】:
所以基本上,它可以在工厂中正常工作,然后我们部署到客户站点,他们的特定基础设施可能会出现问题。谢谢,我会用它作为另一种技巧以上是关于为啥我不能激发 TCP 将 send() 拆分为多个 recv()的主要内容,如果未能解决你的问题,请参考以下文章
TCP 套接字上的 send() 是不是可以返回 >=0 和 <length?