socket_recv没有收到完整数据

时间:2015-07-22 04:52:32

标签: php sockets websocket

我从浏览器通过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

6 个答案:

答案 0 :(得分:7)

1394大约是MTU的常用大小,特别是如果你通过VPN隧道(是吗?)。

您不能期望读取一个呼叫中的所有字节,这些数据包可能会根据网络MTU进行分段。

答案 1 :(得分:7)

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

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

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

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

答案 2 :(得分:7)

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

你的循环检查应该是:

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

Altough我也会检查套接字是否有错误并添加一些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"
    }
}

答案 3 :(得分:0)

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

据我所知,$ client是一个单独的类,所以我无法看到该类的样子或Client :: getHandshake()的作用,但我猜测它是包含websocket升级握手成功或失败的布尔值的getter。

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

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

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

答案 4 :(得分:0)

但是,不是你粘贴的代码部分包含在else块中吗?对我来说,看起来像握手的其他区块没有通过?

您可以将收到的字节打印为字符串吗?

答案 5 :(得分:0)

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

$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";
    }
}

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

Server Class有第三个参数verboseMode,当设置为true时,它将为您提供有关确切情况的详细调试日志。

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