【问题标题】:socket_recv not receiving full datasocket_recv 没有收到完整的数据
【发布时间】:2015-10-11 19:05:59
【问题描述】:

我正在通过 Websocket 从浏览器发送大约 5000 字节的图像数据,但这条线仅接收 1394 字节:

while ($bytes = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT)) {
    $data .= $r_data;
}

这是在正确接收到握手之后。 json 数据在 1394 字节后被截断。 可能是什么原因?

在浏览器界面中,它以 JSON 格式发送图像:

websocket.send(JSON.stringify(request));

浏览器界面很好,因为它可以与我测试过的其他 PHP websocket 免费程序一起使用。

这是完整的source code

【问题讨论】:

  • 好吧,找出问题所在的良好开端是停止忽略socket_recv 可能给出的任何错误。该错误信息实际上可能很有用。通过使用error suppression operator,您将丢弃所有可能有用的错误信息。在尝试随机猜测可能出现的问题之前,我会先从这个开始。
  • 你确定没有gzip编码发生吗?
  • 是的,数据被直接切断了
  • @user5858 什么意思?
  • 在发送的大约 5000 个字节中仅读取 1394 个字节

标签: php sockets websocket


【解决方案1】:

您通过指定 MSG_DONTWAIT 将我们的套接字设置为非阻塞,因此它将在读取第一个数据块后返回 EAGAIN,而不是等待更多数据。移除 MSG_DONTWAIT 标志并改用 MSG_WAITALL,以便它等待接收所有数据。

有几种方法可以知道您是否收到了所有预期的数据:

  1. 发送数据的长度。如果您想发送多个可变长度内容块,这很有用。例如,如果我想发送三个字符串,我可能首先发送一个“3”来告诉接收者需要多少个字符串,然后对于每个字符串,我会发送字符串的长度,然后是字符串数据。
  2. 使用固定长度的消息。如果您期待多条消息,但每条消息的大小相同,那么您可以从套接字读取,直到您至少有那么多字节,然后处理该消息。请注意,您可能会在一次 recv() 调用中收到多条消息(包括部分消息)。
  3. 关闭连接。如果您只发送一条消息,那么您可以半关闭连接。这是有效的,因为 TCP 连接为发送和接收保持不同的状态,因此服务器关闭发送连接,但让接收连接保持打开状态以等待客户端的回复。在这种情况下,服务器将其所有数据发送给客户端,然后调用socket_shutdown(1)

如果您想在接收数据的同时处理数据,则 1 和 2 很有用 - 例如,如果您正在编写游戏、聊天应用程序或其他套接字保持打开并且来回传递多条消息的东西。 #3 是最简单的一种,当您只想一次性接收所有数据时很有用,例如文件下载。

【讨论】:

  • 如果根本没有接收到数据,它只会设置EAGAIN,否则调用将返回接收到的字节数,这可能比它被告知的要少,即使在非阻塞套接字。
  • 对不起,如果我保持循环...在大量 EAGAINS 之后,它确实会在一段时间后读取更多数据(经过几次循环迭代)。你是对的。但是,服务器如何知道是否已接收到完整的有效负载数据并且甚至没有剩余一个字节?
  • 您好,请参阅我关于如何知道何时收到数据的编辑。根据您的需要,有几个选项。
【解决方案2】:

1394 大约是 MTU 的常见大小,尤其是当您通过 VPN 进行隧道传输时(是吗?)。

你不能指望在一次调用中读取所有字节,数据包可能会根据网络 MTU 进行分段。

【讨论】:

  • 没有。我没有使用任何 VPN 或代理。
  • @user5858:答案描述的正确事实涵盖了使用 TCP 套接字的任何类型的传输。在 PHP 中,这至少适用于包装系统调用 revc()socket_recv()man7.org/linux/man-pages/man2/recv.2.html 数据也可以被分割成不超过 1 字节的任意大小的片段。
【解决方案3】:

这只是我的 2 美分。 socket_recv 可以在错误时返回 false。它还可以在非阻塞 IO 中接收零 (0) 个字节。

您在循环中的检查应该是:

while(($bytes = socket_recv($resource, $r_data, 4000, MSG_DONTWAIT)) !== false) {}

尽管我也会检查套接字是否有错误,并添加一些 usleep 调用以防止“CPU 烧毁”。

$data = '';
$done = false;
while(!$done) {
    socket_clear_error($resource);
    $bytes = @socket_recv($resource, $r_data, 4000, MSG_DONTWAIT);

    $lastError = socket_last_error($resource);

    if ($lastError != 11 && $lastError > 0) {
        // something went wrong! do something
        $done = true;
    }
    else if ($bytes === false) {
        // something went wrong also! do something else
        $done = true;
    }
    else if (intval($bytes) > 0) {
        $data .= $r_data;
    }
    else {
        usleep(2000); // prevent "CPU burn"
    }
}

【讨论】:

    【解决方案4】:

    我想知道您的 websockets 连接是否有问题。您在上面引用的 while 循环在我看来位于客户端握手失败的部分代码中,它位于 if($client->getHandshake()) { ... } else { ... }else 中。

    据我所知 $client 是一个单独的类,所以我看不到该类的外观或 Client::getHandshake() 的作用,但我猜它是布尔值的获取器保存 websocket 升级握手的成功或失败。

    如果我是正确的,握手失败并且连接被客户端关闭。从代码中我可以看到您使用的服务器代码需要规范的第 13 版。您没有提及您正在使用哪个客户端库,但其他服务器将接受除此服务器之外的其他版本。

    请确保您的客户端库支持最新版本。

    如果我的建议是错误的,当它获得传入连接并且传输失败时从服务器发布详细输出将会有所帮助。

    【讨论】:

      【解决方案5】:

      但是,您粘贴的代码部分不是包含在 else 块中吗?在我看来握手没有通过的 else 块?

      你能把接收到的字节打印成字符串吗?

      【讨论】:

        【解决方案6】:

        我不认为你的问题是正确的。根据源码,如果握手成功则执行这段代码:

        $data = '';
        
        while (true) {
            $ret = socket_recv($socket, $r_data, 4000, MSG_DONTWAIT);
            if ($ret === false) {
                $this->console("$myidentity socket_recv error");
                exit(0);
            }
            $data .= $r_data;
            if (strlen($data) > 4000) {
                print "breaking as data len is more than 4000\n";
                break;
            } else {
                print "curr datalen=" . strlen($data) . "\n";
            }
        }
        

        如果程序确实进入了您提供的代码部分,那么值得研究一下握手失败的原因。

        服务器类有第三个参数verboseMode,当它设置为 true 时,将为您提供关于究竟发生了什么的详细调试日志。

        我们只是在没有调试日志的情况下进行推测,但如果提供了调试日志,我们可以提出更好的建议。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-28
          • 2017-01-07
          • 1970-01-01
          • 2012-08-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多