伐柯伐柯*其則不遠*我覬之子*籩豆有踐

2 , 5月2018

PHP socket_read函数 PHP_NORMAL_READ和PHP_BINARY_READ两种读取模式的区别

PHP socket*系列函数中socket_read函数第三个参数PHP_NORMAL_READ和PHP_BINARY_READ的用法。

PHP socket_read函数有三个参数,其定义如下:

socket_read ( resource $socket , int $length [, int $type = PHP_BINARY_READ ] ) : string

其中第三个参数“type”定义了读取缓冲区数据的模式

type

Optional type parameter is a named constant:

  • PHP_BINARY_READ (Default) – use the system recv() function. Safe for reading binary data.
  • PHP_NORMAL_READ– reading stops at \n or \r.

默认为PHP_BINARY_READ 根据官方文档描述,此模式为直接调用系统recv()函数读取缓冲数据,是二进制安全的。

那么PHP_NORMAL_READ则不同,它会读到\n或者\r为止。

我们来看一下具体的定义:

if (type == PHP_NORMAL_READ) {
retval = php_read(php_sock, ZSTR_VAL(tmpbuf), length, 0);
} else {
retval = recv(php_sock->bsd_socket, ZSTR_VAL(tmpbuf), length, 0);
}

和官方文档描述一致的是PHP_NORMAL_READ调用的是php_read函数去读取缓冲数据。而PHP_BINARY_READ只是简单的直接调用了recv()

php_read是PHP封装的读取方式,我们可以看一下它的定义:

php_read(php_socket *sock, void *buf, size_t maxlen, int flags)
{
*t = '\0';
while (*t != '\n' && *t != '\r' && n < maxlen) { 
//读到 \n 或者 \r 就算到了包尾
// 注意 n >= maxlen 并没有做处理,当你的包比如说"xxx\n"本身长度就已经超过接收长度,后面的就被丢弃了。包是不完整的。
if (m > 0) {
t++;
n++;
} else if (m == 0) {
no_read++;
if (nonblock && no_read >= 2) {
return n;
/* The first pass, m always is 0, so no_read becomes 1
* in the first pass. no_read becomes 2 in the second pass,
* and if this is nonblocking, we should return.. */
}

if (n < maxlen) { // 一个的读
m = recv(sock->bsd_socket, (void *) t, 1, flags);
}

return n;
}

由此可见通过封装php_read提供了一种逐行读的能力。但是使用时请务必注意,如代码所示,这也带来了两个问题:

第一,如果你发包不带\r 或者\n 它会一直读完整个定义长度, 即$length。 正常来说肯定是约定好的,那么是会带,但是如果超过了读取长度,那还是会被截断,这个逐行读就没意义了,又回到了拆包或者先行确定长度的老路上。

第二,报文不可包含换行符。

第三,如果没有带\r 或者\n (忘记或者恶意),会导致这个位置永远读不完。

下面有简单的测试:

client发100个包过去:

for ($i = 10; $i < 100; $i++) {
    $bodyLen = 300;
    $data = json_encode([
        "id" => $i,
        "data" => range(0, 10)
    ]);

    $arr = implode("", range(0, 100));
    $data = substr($arr, 0, 300);
    echo $data.PHP_EOL;

    $message = pack("Na" . $bodyLen, $bodyLen, $data);//一个包长度是1028=4+1024
//    $message.="\n"; 测试忘记带结束符
    $len = socket_write($socket, $message);
    printf("[%d]send len %d \n", $i, $len);
}

服务端:

$data = socket_read($socket, 1024, PHP_NORMAL_READ);
$dataLen = strlen($data);
printf("recv package len : %d data: %s \n", $dataLen, $data);
if($dataLen > 4){
    $header = substr($data, 0, 4);
    $bodyLen = unpack("Nlen", $header)['len'];

    if($dataLen >= 4 + $bodyLen){
        $body = substr($data, 4, 4 + $bodyLen);
        $message = unpack(sprintf("a%dbody", $bodyLen), $body)['body'];
        printf("recv package : %s \n", $message);
    }
}else{
    break;
}

服务端打印数据

略
cnt--------------------25
recv package len : 1024 data: ,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100,012345678910111213141516171819202122232425262728293031323334
recv package :
cnt--------------------26
recv package len : 1024 data: 3536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
cnt--------------------27

正常的包数据是0-100.

由此可见数据少了。

把服务端改成PHP_BINARY_READ,可以完整收到。

cnt--------------------89
recv package len : 305 data: ,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100

recv package : 0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
cnt--------------------90
recv package len : 305 data: ,0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100

recv package : 0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
cnt--------------------91

也很明显的可以看出用PHP_NORMAL_READ的好处。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

© 2011 - 2024 laijim.com