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