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的好处。