您的位置:首页 > 运维架构

为什么用fopen打开远程URL会很慢?

2015-10-24 11:45 183 查看

1. HTTP HEADER之诡

$content = file_get_contents('http://www.baidu.com/logo.png');

这是php中一种读取网络图片的方式,就跟读本地文件一样,用起来十分方便。但是这种方式存在一个问题,部分网络地址读起来速度非常快,但总有少数网址,读起来要卡顿10秒、20秒甚至1分钟。网上对于这个问题有很多的讨论,但答案基本都有问题。

对于慢,最常见的解释是DNS查询慢。的确,通过curl和wget的对比可以发现,获取同一个文件,curl需要0.5s,而wget需要1.5s,从wget的屏幕输出上看,其中1秒钟被他花在了DNS查询上。虽然DNS查询会占据1s的时间,但也不应该卡顿10秒呀。其中必有蹊跷。

国外有个同学终于给出了一个靠谱的答案:
By analyzing it with Wireshark, the issue (in my case and probably
yours too) was that the remote web server DIDN'T CLOSE THE TCP
CONNECTION UNTIL 15 SECONDS (i.e. "keep-alive").

Indeed, file_get_contents doesn't send a "connection" HTTP header, so
the remote web server considers by default that's it's a keep-alive
connection and doesn't close the TCP stream until 15 seconds (It might
not be a standard value - depends on the server conf).

A normal browser would consider the page is fully loaded if the HTTP
payload length reaches the length specified in the response
Content-Length HTTP header. File_get_contents doesn't do this and
that's a shame.


问题出在file_get_contents的行为上:(1)它发起http连接的时候,没有带http头,(2)它接收完所有数据后,没有主动断开和服务器的http连接。
找到问题原因之后,解决方案就相对简单了:让file_get_contents也带上一个http的头,并且在头里面标注:Connection: close; 要求服务器发完数据后立即断开连接。

file_get_contents原型如下:
string file_get_contents (
string $filename [,
bool $use_include_path = false
[, resource $context
[, int $offset = -1
[, int $maxlen ]]]] )


其中$context参数就是用来传递http头的。代码如下:
$opts = array(
'http'=>array(
'method'=>"GET",
'Connection'=>"close\r\n"
)
);
$context = stream_context_create($opts);
file_get_contents($filename, false, $context);


添加上$context后,性能立即得到了提升,每次读请求只需要1~2秒了。

注1:file_get_contents实际上是对fopen、fread、fclose的一个封装,所以它存在的问题,在fopen上同样存在。对于fopen来说,解决方案也是相同的。

2. DNS之谜

经过添加$option,性能已经得到了很大的提升。这时候DNS查询带来的开销就不可忽视了。如何消除掉这1秒的开销?目前我还没有基于fopen、file_get_contents的方案,只能用curl方案了。curl方案既不存在上面的HTTP
HEADER问题,也不存在DNS问题,是个非常不错的方案。

如果你选择用curl方案,同时还希望用上stream接口来读数据,可以了解一下curl的CUROPT_FILE这个option。
curl_setopt($ch, CURLOPT_FILE, $fp);


2. 总结

本文提出了解决网络读取图片慢的两种方案,对于移动开发、爬虫开发,微信开发,支付宝开发等各领域的朋友都有借鉴意义。

本文还让我重新思考了一个方法论:当网上搜不到答案时,外国友人用Wireshark监听数据包的方式其实是最直接合理高效的。为什么我没有这么做?想想,还是以前总结出来的工具论,我对这些工具的使用不熟练,不会往这个方向上想,就算想了,也不愿意行动。应该多动手,多学习!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: