【问题标题】:HTTP in C: Why do I have to call recv() multiple times?C 中的 HTTP:为什么我必须多次调用 recv()?
【发布时间】:2014-02-16 16:17:27
【问题描述】:

我已经开始学习 C(套接字)中的网络编程和 Internet 协议。

目前我专注于 HTTP。

在我使用 C 实现 HTTP 客户端的(非常)短暂的经验中,我发现有时您必须多次调用 recv() 才能从服务器获取整个消息 - 即使客户端没有发送在对 recv() 的单独调用之间向服务器发送消息。

例如:

当我尝试通过一次调用 recv() 来实现 HTTP 客户端时,我只得到了标头(我想,我仍然是 HTTP 的新手)。 但是当我两次调用 recv() 时,我在第一次调用中得到了标题,在第二次调用中得到了正文(html 代码)。

这不是缓冲区短的问题,因为我使用的缓冲区足够长,可以容纳整个消息。

为什么会这样? 尽管客户端没有发送新数据,但客户端必须多次调用 recv() 的原因是什么? 我以为如果客户端不向服务端发送新数据,调用recv()会带来服务端的全部响应。

我不认为这是我的代码的问题,但是如果您要索取代码,我可以在这里发布。我只是觉得这是不必要的,如果我错了,请纠正我。

我认为这无关紧要,但我正在使用 Winsock2。

【问题讨论】:

  • 原始 I/O 函数通常必须总是在循环中调用,因为它们不保证任何成功。
  • 但是第一次调用成功了——我在第一次调用中得到了标题。另外,什么是“原始函数”?谢谢。
  • 但它“成功”了,但不能保证它读了多少。它最多可以读取一个字节并“成功”。所以你必须继续打电话,直到它说“没有更多数据,连接关闭”。 “原始”是指运营服务,如 readrecvwrite。相比之下,像 printffwrite 这样的高级库函数只需要调用一次,因为它们承诺写入整个数据集(它们会在内部进行循环)。
  • 感谢您对“原始”函数的解释。我怎么知道所有的数据都收到了?检查 recv() 返回 0 是否正常?
  • 仔细阅读recv()/send() 的手册页,并了解至少对于套接字,这两个函数不一定接收/发送尽可能多的字节,但很少。因此,循环计算此类调用,直到所有数据或终止符已被接收/发送是一个好主意,而不是说必不可少的必要性。

标签: c sockets http web winsock2


【解决方案1】:

TCP 被设计为面向流。接收者对发送的消息/数据包一无所知。它所看到的只是一个简单的字节流。

所以你认为“我发送了一条消息”,但 recv() 只收到了一半。事实是,您从未发送过消息。你发送了一堆字节,你不能指望一次调用就能接收到所有字节。您可以在 1 个、2 个或多个电话中收到它们;您可以收到前一个“消息”的结尾以及下一个“消息”的开头,依此类推。

唯一的保证是您将按照发送的顺序接收字节,并且在流关闭之前您永远不会收到 0 字节。这就是网络 API 的工作方式,您必须习惯它。

【讨论】:

  • 我还是不明白。你说'TCP是一个流'是什么意思?它与我的问题有什么关系?感谢您的帮助。
  • 就像一个流,如果你想要所有的内容,你必须继续阅读,直到你得到一个流结束的指示。没有关于如何在这些读取中分配数据的承诺。这纯粹是代码的编写方式和使用的缓冲区大小的产物。
  • 嗯,现在更清楚了。另一个相关问题:在我的例子中,第一个调用得到了“标题”,第二个调用得到了“正文”——html 代码。这是否意味着服务器“调用 send()”两次:第一次调用带有 header,第二次调用带有 body(html)?
  • 接收者不知道 shit 服务器做了多少次发送()。所以你不能据此得出任何结论。它只是一个字节流。没有标记、块、消息......
  • 服务器可能做了两次发送()。关键是:这不关你的事。
【解决方案2】:

接收者不知道发送调用的次数和每次调用的字节数
在发件人处制作。你不能说你想要一个 10 字节的 recv
每次发送 10 个字节。
一个recv调用可以接收三个发送的数据,
或者一次发送的数据可以分成三个接收...
如果他们有时匹配,那纯属巧合。

【讨论】:

  • 在我的例子中,第一个调用得到了“headers”,第二个调用得到了“body”——html 代码。这是否意味着服务器“调用 send()”两次:第一次调用带有 header,第二次调用带有 body(html)?
  • @Programmer 可能是的,但不确定。您唯一可以确定的是您收到的字节顺序与服务器发送的相同。
  • @Programmer:可能是这样,但不能保证。正如我所说,发送和接收呼叫的数量并不匹配。所有发送的数据在某个点连接在一起,然后在接收器处再次拆分,操作系统认为它喜欢它。这只是客户端操作系统等的决定,不受连接发送部分的影响
【解决方案3】:

您可以做的一件事是使用这样的循环。

    while((recv(socketi,response,sizeof(response)-1,0))>0){
    }
    printf("\n%s",response);

在您发送的请求中,您必须明确说明您正在使用 HTTP/1.0 GET / HTTP/1.0(在收到所有数据后关闭连接)。否则会等到超时发生。

通过这样做,您将获得数组中的所有响应。无需多次调用 recv()。

【讨论】:

    猜你喜欢
    • 2021-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多