我通过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免费程序一起工作。
通过指定MSG_DONTWAIT将套接字设置为非阻塞,因此它将在读取第一个数据块后返回EAGAIN,而不是等待更多数据。删除MSG_DONTWAIT标志并使用MSG_WAITALL代替,以便等待接收到的所有数据。
有几种方法可以知道你是否收到了所有你想要的数据:
- 发送数据的长度。如果你想发送多个可变长度的内容块,这是很有用的。例如,如果我想发送三个字符串,我可以先发送一个"3"来告诉接收者期望有多少个字符串,然后对于每个字符串,我将发送字符串的长度,后跟字符串数据。
- 使用固定长度的消息。如果您期望有多个消息,但每个消息的大小相同,那么您可以从套接字中读取,直到至少有那么多字节,然后处理消息。请注意,在单个recv()调用中可能会收到多条消息(包括部分消息)。
- 关闭连接。如果您只发送一条消息,那么您可以半关闭连接。这是有效的,因为TCP连接为发送和接收保持单独的状态,因此服务器和关闭发送连接,但保留接收连接,以等待客户端的回复。在这种情况下,服务器将其所有数据发送给客户端,然后调用socket_shutdown(1)
1和2是有用的——例如,如果你正在编写一个游戏,聊天应用程序,或者其他socket保持打开状态,并且多个消息来回传递的地方。#3是最简单的,当您只想一次性接收所有数据时(例如文件下载)非常有用。
1394大约是MTU的一般大小,特别是如果你是通过VPN隧道(是吗?)
你不能期望在一次调用中读取所有字节,数据包可能会根据网络MTU分片。
这只是我个人的看法。Socket_recv可以在出现错误时返回false。它也可以在非阻塞IO中接收零(0)字节。
你在循环中的检查应该是:
while(($bytes = socket_recv($resource, $r_data, 4000, MSG_DONTWAIT)) !== false) {}
尽管我也会检查套接字的错误,并添加一些ussleep调用来防止"CPU burn"。
$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"
}
}
我想知道你是否有问题与你的websockets连接。你上面引用的while循环在我看来是驻留在客户端握手失败的代码的一部分,它在if($client->getHandshake()) { ... } else { ... }
的else
中。
据我所知,$client是一个单独的类,所以我看不出这个类是什么样子的,也看不出client::getHandshake()是做什么的,但我猜它是一个布尔值的getter,它持有websocket升级握手的成功或失败。
如果我是正确的,握手失败,连接被客户端关闭。从代码中我可以看到你使用的服务器代码需要版本13的规范,你没有提到你正在使用哪个客户端库,但其他服务器将接受其他版本的服务器。
请确保您的客户端库支持最新版本。
如果我的建议是错误的,则在服务器获得传入连接并且传输失败时发布服务器的详细输出将会有所帮助。
但是,您粘贴的代码部分不包含在else块中吗?在我看来握手没有通过的其他障碍?
能否将接收到的字节打印为字符串?
我认为你的问题不对。根据源代码,如果握手成功,则执行这段代码:
$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 4000n";
break;
} else {
print "curr datalen=" . strlen($data) . "n";
}
}
如果程序真的进入了你提供的代码部分,那么就有必要研究一下握手失败的原因。
服务器类有第三个参数verboseMode
,当设置为true时,将为您提供详细的调试日志,了解究竟发生了什么。
我们只是在没有调试日志的情况下进行推测,但如果提供了调试日志,我们可以提出更好的建议。