数据包没有固有的长度,即使从 C 中获取这些信息实际上也相对困难。这是因为在用户层,套接字数据不是以数据包的形式表示的。 (UDP 以完整的有效负载表示,但是在用户空间中接收到的单个 UDP 有效负载绝对有可能仍然表示多个数据包。)因此,这个问题并不正确,您真正应该问的是如何确定如何在套接字上可以读取许多字节。
那么为什么你粘贴的代码没有告诉你呢?您应该真正了解这段代码的作用,因为它很有趣。但是,由于它与您可能真正感兴趣的信息有些相干,所以我会留到以后再说。
不幸的是,PHP 没有为您提供使用原始套接字 FD 调用 ioctl(2) 的方法。这将允许您在 PHP-land 中执行类似ioctl(sock, FIONREAD, &n) 的操作,以确定可供读取的字节数。 (实际上,如果你使用fopen 或fsockopen,显然你可以这样做,但我猜你没有这样做。)唉,这行不通。
幸运的是,您有两种选择:
使用非阻塞套接字。您可以在您的套接字流上调用socket_set_nonblock。一旦你这样做了,任何对socket_read、socket_write等的调用都将在非阻塞模式下运行。这意味着如果你这样做,例如$data = socket_read($socket, 1024),并且 少于 可用字节数少于 1024,则返回可用字节数。 (注意,如果没有可用数据,则返回的字节数可能为 0。)
使用socket_select 对一系列套接字执行相同的操作。此函数会通知您哪些套接字具有可读数据/处于可写状态/有需要处理的错误。
无论您使用哪个版本,处理套接字未接收到足够数据的情况的一般方法是实现超时。 socket_select 为此提供了本机接口;使用非阻塞套接字手动执行此操作需要您记住在调用 socket_read 之间等待了多长时间并实现休眠。如果您在一段时间内(比如 10 秒)没有收到足够的数据,请关闭套接字并忘记它。
如果您收到的数据多于您的预期,那就是一个错误,您关闭套接字并忘记它。
您处理的协议也很重要。因为你没有说你正在处理什么协议,所以我无法帮助你告诉你需要多少数据。但也许你正在实现自己的协议是为了好玩。
在这种情况下,您需要确定编码在线数据量的方法。因为你说你在你的问题中使用的方法做了一些二进制技巧,所以我也会这样做。您可能希望将pack 一个32 位值放入字符串的开头。当您收到连接时,您会等待前 4 个字节进入。一旦您读取了这 4 个字节,您就可以unpack 来确定您需要读取多少数据。
<?php
$payload = "Have a nice day!\n";
$len = strlen($payload) + 1; // + 1 for terminating NUL byte
$packet = pack("Na", $len, $payload);
socket_send($sock, $packet, $len + 4); // + 4 is length
...
然后,在服务器中
<?php
$r = socket_read($sock, 4);
$la = unpack("N", $r);
// Because we don't know how much to read until we get the first 4 bytes.
// Obviously this is a DoS vector for someone to hold the connection open,
// so you will likely want to use socket_select to get that first bit of
// data. That's an exercise for you.
socket_set_nonblock($sock);
$len = $la[1];
$time = 0;
$payload = "";
while ($len > 0 && $time < 10) {
$data = socket_read($sock, $la[1]);
$tlen = strlen($data);
$payload .= $data;
$len -= $tlen;
if ($len == 0) {
break;
}
sleep(1); // Feel free to usleep.
$time++;
}
注意我没有测试这段代码,确保我正确编码了打包/解包的数据,所以我不确定你是否可以逐字使用它。将其视为架构伪代码。
其他协议有其他长度编码方式。例如 HTTP 在常见情况下使用 Content-Length。
您的示例代码
在之前的编辑中,我只是将其视为查看第一个字节来获取长度。我重新检查了这个问题,因为我看到了一个赞成票,并且因为我关于数据包的一些措辞让我感到困扰。而且我还意识到我快速浏览了该代码后得出的结论是非常错误的。
代码从套接字读取单个字节以尝试获取可变长度的剩余有效负载。 (我想知道这是来自 ZeroMQ 或 Apple 推送通知或其他东西的存储库的代码。)无论如何,看起来代码做了一些奇怪的事情,但实际上发生了什么?
private function get_packet_length($socket) {
$a = 0;
$b = 0;
while(true) {
/* Read next single byte off of the socket */
$c = socket_read($socket, 1);
if (!$c) {
return 0;
}
/* Use integer value of the byte instead of the character value */
$c = ord($c);
/*
* Get the first 7 bits of $c. Since $c represents an integer value
* of a single byte, its maximum range is [0, 2^8). When we use only
* 7 bits, the range is constrained to [0, 2^7), or 0 - 127. This
* means we are using the 8th bit as a flag of some kind -- more on
* this momentarily.
*
* The next bit executed is ($b++ * 7), since multiplication has
* higher precedence than a left shift. On the first iteration, we
* shift by 0 bits, the second we shift 7 bits, the third we shift
* 14 bits, etc. This means that we're incrementally building an
* integer value byte by byte. We'll take a look at how this works
* out on real byte sequences in a sec.
*/
$a |= ($c & 0x7F) << $b++ * 7;
/*
* If we've tried to handle more than 5 bytes, this encoding doesn't
* make sense.
*/
if ($b > 5) {
return false;
}
/*
* If the top bit was 1, then we still have more bytes to read to
* represent this number. Otherwise, we are done reading this
* number.
*/
if (($c & 0x80) != 128) {
break;
}
}
return $a;
}
让我们考虑一下这对几个不同的字节流意味着什么:
$stream = "\x01"; /* This is pretty obviously 1 */
$stream = "\x81\x80\x80\x00";
/* This is also 1, can you figure out why? */
$stream = "\xff\x01"; /* You might think it 256, but this is 255 */
$stream = "\x80\x82"; /* This is 256. */
$stream = "\xff\xff\x01"; /* 32767 */
$stream = "\x80\x80\x02"; /* 32768 */
$stream = "\x0c\x00\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21"
/*
* A static value 13, a length terminator, and 13 bytes that happen
* to represent "Hello, world!".
* This shows how such an encoding would be used in practice
*/
如果您熟悉字节顺序,您可能会注意到这些值是以 little-endian 编码传输的。这通常很奇怪(网络字节顺序是大端序),但我们实际上并没有发送整个整数值:我们发送的是可变长度的字节流。每个字节的编码帮助我们弄清楚长度是多少。但是,在不知道这段代码实现什么协议的情况下,这实际上可能是一个错误,它会阻止这段代码在具有不同字节序的机器上可移植地工作。
请务必注意,这是字节流协议的一部分,并不是获取任何长度的标准方法。事实上,很多二进制协议并没有被定义为字节流协议。这是因为字节流协议通常根本不定义字节顺序(因为流不需要它)。因此,如果您在 PPC 或某些 ARM 处理器上运行此代码,它将无法工作。因此,我们说此代码不可移植。
在处理字节流或通过网络发送原始二进制数据时,请始终确保定义数据的字节顺序。如果不这样做,您的协议将创建不可移植的实现。另请参阅 Rob Pike 的this great post,以获取有关字节顺序主题的更多信息,以及为什么任何时候出现问题,您要么感到困惑,要么有人做错了(例如在没有完全定义数字编码的情况下定义字节流协议) .