我一直在并排运行两个套接字客户端,收集 http 流数据(不是 Twitter,而是类似的东西)。数据以分块编码传递。
其中一个客户端是 curl(在命令行上,而不是 php-curl),http 和 https 都可以正常工作。另一个是我自己的 PHP 脚本,使用 fsockopen
和 fgets
。适用于 https,但我对 http 有特定问题。有多具体?仅当流安静 60 秒时才会发生。如果只有 50 秒的安静,它就可以正常工作。我一直在将发送和接收的 curl 的 http header 与我的脚本进行比较,并消除了所有差异。我以为我知道关于 PHP 套接字的所有知识,尤其是分块编码,但现在是吃不起眼的馅饼的时候了,因为这个让我难住了。
因此,使用“--trace - --trace-time”运行 curl,我看到在 60 秒静默期后第一个数据包通过了:
05:56:57.025023 <= Recv data, 136 bytes (0x88)
0000: 38 32 0d 0a 7b 22 64 61 74 61 66 65 65 64 22 3a 82..{"datafeed":
0010: 22 64 65 6d 6f 2e 31 64 36 2e 31 6d 2e 72 61 6e "demo.1d6.1m.ran
...
0080: 34 22 7d 5d 7d 0a 0d 0a 4"}]}...
82 是 block 大小的十六进制。\r\n 标记 block 大小行的结尾。 block 从“{”开始。
在 PHP 端,我的循环是这样开始的:
while(true){
if(feof($fp)){fclose($fp);return "Remote server has closed\n";}
$chunk_info=trim(fgets($fp)); //First line is hex digits giving us the length
$len=hexdec($chunk_info); //$len includes the \r\n at the end of the chunk (despite what wikipedia says)
对于 https,或者小于 60 秒的间隔,这工作正常,$len 是 100 或任何 block 大小。 但是,在那 60 秒的间隔之后,我在 $chunk_info 中得到的是:
datafeed":"demo.1d6.1m.ran...
所以,我似乎丢失了前六个字节:38 32 0d 0a 7b 22
所有后续 block 都很好,并且与 curl 接收到的完全相同。
版本详情
curl 7.19.7 (x86_64-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15 协议(protocol):tftp ftp telnet dict ldap ldaps http 文件 https ftps 特性:GSS-协商 IDN IPv6 大文件 NTLM SSL libz
带有 Suhosin-Patch (cli) 的 PHP 5.3.2-1ubuntu4.18(构建时间:2012 年 9 月 12 日 19:12:47)
服务器:Apache/2.2.14 (Ubuntu)
(到目前为止,我只测试过本地主机连接。)
循环的其余部分如下所示:
$s='';
$len+=2; //For the \r\n at the end of the chunk
while(!feof($fp)){
$s.=fread($fp,$len-strlen($s));
if(strlen($s)>=$len)break; //TODO: Can never be >$len, only ==$len??
}
$s=substr($s,0,-2);
if(!$s)continue;
$d=json_decode($s);
//Do something with $d here
}
(另外:在我目前测试的方式中,代码在 60 秒静默期之前恰好通过此循环一次。)
注意:我有很多解决方法来让事情正常进行:例如强制使用 https,或使用 curl-php。这个问题是因为我想知道发生了什么,知道 60 秒后发生了什么变化,并学习如何阻止它发生。或许还能学到新的故障排除思路。将其视为血腥的求知欲:-)
最佳答案
这是错误修复:
$chunk_info=trim(fgets($fp)); //First line is hex digits giving us the length
if($chunk_info=='')continue; //Usually means the default 60 second time-out on fgets() was reached.
...
如果 fgets($fp) 返回东西,那么你有一个 block 要读取。如果那个东西是零,那么你有一个空白 block 要处理。但是当它返回nothing 时,这意味着 fgets 已经超时。 tcp://的默认超时时间似乎是 60 秒;而 ssl://的默认超时时间更长(抱歉,我还没有追踪到它是什么 - 它可能会永远阻塞)。
当没有可读取的数据 block 时尝试处理数据 block ,一切都变得不同步。因此被盗 6 个字节。
故障排除提示:
- 在代码中添加:
echo "**".date("Y-m-d H:i:s");print_r(stream_get_meta_data($fp));ob_flush();flush();
元数据有一个条目说明最后一个流操作何时超时。日期戳必不可少。 - 在命令行中使用
tcpdump -A -i lo port http
进行交叉引用。将时间戳与 PHP 调试行中的时间戳进行比较,使我能够发现可疑行为。
关于php - 丢失 6 个字节,但前提是套接字安静 60 秒?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13832646/