【问题标题】:PHP — Get Packet LengthPHP — 获取数据包长度
【发布时间】:2015-02-22 05:22:34
【问题描述】:

我目前正在处理 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"

【问题讨论】:

标签: php sockets udp


【解决方案1】:

数据包没有固有的长度,即使从 C 中获取这些信息实际上也相对困难。这是因为在用户层,套接字数据不是以数据包的形式表示的。 (UDP 以完整的有效负载表示,但是在用户空间中接收到的单个 UDP 有效负载绝对有可能仍然表示多个数据包。)因此,这个问题并不正确,您真正应该问的是如何确定如何在套接字上可以读取许多字节。

那么为什么你粘贴的代码没有告诉你呢?您应该真正了解这段代码的作用,因为它很有趣。但是,由于它与您可能真正感兴趣的信息有些相干,所以我会留到以后再说。

不幸的是,PHP 没有为您提供使用原始套接字 FD 调用 ioctl(2) 的方法。这将允许您在 PHP-land 中执行类似ioctl(sock, FIONREAD, &amp;n) 的操作,以确定可供读取的字节数。 (实际上,如果你使用fopenfsockopen,显然你可以这样做,但我猜你没有这样做。)唉,这行不通。

幸运的是,您有两种选择:

  1. 使用非阻塞套接字。您可以在您的套接字流上调用socket_set_nonblock。一旦你这样做了,任何对socket_readsocket_write等的调用都将在非阻塞模式下运行。这意味着如果你这样做,例如$data = socket_read($socket, 1024),并且 少于 可用字节数少于 1024,则返回可用字节数。 (注意,如果没有可用数据,则返回的字节数可能为 0。)

  2. 使用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,以获取有关字节顺序主题的更多信息,以及为什么任何时候出现问题,您要么感到困惑,要么有人做错了(例如在没有完全定义数字编码的情况下定义字节流协议) .

【讨论】:

    【解决方案2】:

    socket_read 返回一个字符串这么简单

    $packet_length=strlen($c);
    

    应该工作

    另外,你得到 int(1) 的原因是你在 socket_read 中设置了长度参数。 删除它可能会为您节省一些麻烦

    这个

            $c = socket_read($socket, 1);
    

    应该是

            $c = socket_read($socket);
    

    【讨论】:

    • 1) socket_read() 需要2个参数;根据php.net 2) 我将用我的新代码和输出更新我的 OP。
    【解决方案3】:

    使用

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

    【讨论】:

    • sock_reack 将尝试读取到您定义的限制 - 这里它将读取 1024 字节(如果没有更多可用,则更少)
    猜你喜欢
    • 1970-01-01
    • 2010-10-04
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多