Dar*_*ook 6 php sockets curl chunked-encoding
我一直在并行运行两个套接字客户端,收集http流数据(不是Twitter,但类似的东西).数据通过分块编码实现.
其中一个客户端是curl(在命令行上,而不是php-curl),其中http和https工作正常.另一个是我自己的PHP脚本,使用fsockopen和fgets.适用于https,但我有一个特定的问题与http.具体怎么样?只有当流静止60秒时才会发生这种情况.如果只有50秒的安静,它工作正常.我一直在将curl发送和接收的http标头与我的脚本进行比较,并删除了所有差异.我以为我知道有关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"}]}...
Run Code Online (Sandbox Code Playgroud)
对于块的大小,82是十六进制.\ r \n标记块大小行的结尾.块从"{"开始.
在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)
Run Code Online (Sandbox Code Playgroud)
使用https,或者间隔小于60秒,这样可以正常工作,$ len是100或者块大小.但是,在那60秒的差距之后,我在$ chunk_info中得到的是:
datafeed":"demo.1d6.1m.ran...
Run Code Online (Sandbox Code Playgroud)
所以,我似乎丢失了前六个字节: 38 32 0d 0a 7b 22
所有后续的块都很好,与卷曲接收的块完全相同.
版本细节
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协议:tftp ftp telnet dict ldap ldaps http file https ftps功能:GSS-Negotiate IDN IPv6 Largefile NTLM SSL libz
PHP 5.3.2-1ubuntu4.18与Suhosin-Patch(cli)(内置:2012年9月12日19:12:47)
服务器:Apache/2.2.14(Ubuntu)
(到目前为止,我只测试了localhost连接.)
循环的其余部分如下所示:
$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
}
Run Code Online (Sandbox Code Playgroud)
(旁白:在我到目前为止测试的方式中,代码在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.
...
Run Code Online (Sandbox Code Playgroud)
如果 fgets($fp) 返回某些内容,那么您就有一个块需要读取。如果该值为零,那么您就有一个空白块需要处理。但是当它没有返回任何内容时,意味着 fgets 已超时。tcp:// 的默认超时时间似乎是 60 秒;而 ssl:// 的默认超时时间更长(抱歉,我还没有找到它是什么 - 它可能会永远阻塞)。
当没有可读取的数据块时尝试处理数据块,一切都会变得不同步。因此被盗了 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 中的调试行中的时间戳进行比较,使我能够发现可疑行为。