我目前正在处理PHP中的套接字和数据包,并且不知道从哪里开始获取数据包的长度。我从GitHub回购中试过这个(不记得是哪一个):
private function get_packet_length($socket) {
$a = 0;
$b = 0;
while(true) {
$c = socket_read($socket, 1);
if (!$c) {
return 0;
}
$c = ord($c);
$a |= ($c & 0x7F) << $b++ * 7;
if ($b > 5) {
return false;
}
if (($c & 0x80) != 128) {
break;
}
}
return $a;
}
有谁知道为什么这不起作用?我得到int(1)
。
修改
private function get_packet_length($socket) {
$a = 0;
$b = 0;
while(true) {
$c = socket_read($socket, 10240);
if (!$c) {
return 0;
}
$c = ord($c);
$a .= strlen($c);
if ($b > 5) {
return false;
}
if (($c & 0x80) != 128) {
break;
}
}
return $a;
}
输出:string(2) "01"
答案 0 :(得分:6)
数据包没有固有长度,即使从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字节可用,返回可用字节。 (N.B.如果没有可用数据,则返回的字节数可以为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++;
}
N.B。我没有测试过这段代码,确保我正确编码打包/解包后的数据,所以我不确定你可以逐字使用它。将其视为建筑伪代码。
其他协议具有其他长度编码方式。例如,HTTP在常见情况下使用Content-Length。
在之前的编辑中,我只是将其视为第一个获取长度的字节。我回过头来看看这个问题,因为我看到了一个upvote,因为我的一些关于数据包的措辞让我困扰。我也很清楚地看到那段代码后,我发现自己的结论非常错误。
代码读取套接字的各个字节以尝试获取可变长度的剩余有效负载。 (我想知道这是来自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,了解有关字节顺序主题的更多信息,以及为什么任何时候出现问题,您都会感到困惑或有人做错了(比如没有定义字节流协议)完全定义数字的编码。)
答案 1 :(得分:0)
socket_read返回一个简单的字符串
$packet_length=strlen($c);
应该有效
另外,你获得int(1)的原因是你在socket_read中设置了length参数。 删除它可能会省去一些麻烦
:此强>
$c = socket_read($socket, 1);
应
$c = socket_read($socket);
答案 2 :(得分:0)
使用
$c = socket_read($socket, 1024); //1kb read.
if ($c === "") //from documentation - no data is empty string.
break;
我不知道你想要收到什么,但是如果字符串可以使用
$str = '';
while(true) {
$tmp = socket_read($socket, 1024);
if ($tmp === '')
break;
$str .= $tmp;
}
return $str;
}