PHP的fsockopen函数详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP的fsockopen函数详解相关的知识,希望对你有一定的参考价值。

先来看看手册是如何定义fsockopen函数的。

fsockopen打开一个网络连接或者一个Unix套接字连接

resource fsockopen    ( string $hostname   [, int $port = -1   [, int &$errno   [, string &$errstr   [, float $timeout = ini_get("default_socket_timeout")  ]]]]

使用fsockopen方法和使用CURL方法的实质都是去模拟HTTP协议,所以重点在于如果去构造HTTP协议。

注意:要使用此函数的功能,需要在php.ini文件中打开allow_url_open

//打开连接通道
$fp=fsockopen(‘www.cnblogs.com‘,80,$errno,$errstr,30);
if(!$fp){
    die(‘连接错误‘.$errstr);
}
set_time_limit(0);
//通道打开,模拟HTTP请求,http协议标准的换行是\r\n
$http="GET / HTTP/1.1\r\n";
$http.="Host: www.cnblogs.com\r\n";
$http.="User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
$http.="Connection: Close\r\n\r\n";

//http模拟完成,发送给服务器
fwrite($fp,$http);
//接受服务器的返回结果

$out=‘‘;
while(!feof($fp)){
    $out.=fread($fp,1024);
}

echo $out;
fclose($fp);

在开发过程中常常遇到这样的需求,模拟浏览器访问某接口,并获取返回数据。我们比较常使用的方法是fsockopen与接口建立连接,然后发出指令,然后通过fgets接受返回值。

但是我们发现,通过PHP模拟访问接口往往比浏览器访问同样的接口慢很多。这个问题困扰过我很久,今天终于找到原因了。我看网上很多朋友有同样的问题,分享出来供大家参考。

我们常常写这样的代码:

1while(!feof($sHnd)) {
2    $line = fgets($sHnd, 4096);
3}

fgets会获取文件描述符$sHnd的当前的4096(也可能是别的常数)个字节,如果还没有到4096个字节遇到换行符了,则只返回换行符及换行符之前的内容。

许多文档教程里也都是这么讲的,这段代码许多情况下也能正常执行。我一步一步跟踪PHP语句的耗时,发现前面若干次的fgets都很快,最耗时的是最后一次fgets,有时长达几秒,有时长达十几秒。

原来这是服务器的KeepAlive功 能造成的,Apache有这么一个设置(nginx等其他web服务器也都有):KeepAlive,如果这个设置置为On,则完成一次请求后,服务器并 不会关闭TCP连接,而是保持连接等待浏览器下次发起其他请求时直接利用这个连接。但是当fgets获取最后一段内容时没有发现换行符,也没有文件结束标 志(feof()),所以fgets获取完内容后仍继续等待,希望遇到换行符或者其他内容以达到4096个字符。于是,就这样服务器和PHP耗上了,互相 等待。耗了一会后,服务器先耗不起了,毕竟服务器的连接数很宝贵,当连接若干秒没有活动,就会关掉这个连接(Apache通过 KeepAliveTimeout这个选项进行设置,这个值通常为5-15)。服务器关掉连接之后,PHP这边的fgets这才失落得返回最后一批内容, 访问接口过程结束。

清楚了慢的原因就知道了解决方案了:

服务器返回的HTTP头中包含有内容长度这个属性,当已接受的内容长度与之相等时,我们就可以断定:接口内容已经获取完毕,不必再等了。具体做法 是:每次获取不超过剩余总长度的内容(min(4096, $leftlength))。剩余总长度为0时,跳出while(feof($xxxxx))的循环。

经过这样的修改,php通过sock方式访问接口速度慢的问题已经基本解决了,但还不够完美,继续速度优化的思路还在KeepAlive上。

大家都知道访问接口的耗时相当一部分是浪费在建立连接上,如果我们需要频繁调用接口的话,还有很大的优化余地。既然服务器保持了连接,那如果PHP 也把连接保存下来那是不是就不用建立连接了?答案是肯定的:第一次访问接口时使用pfsockopen(pfsockopen与fsockopen唯一的 区别就是它建立的是长连接)函数建立与服务器的连接,在访问完成后不关掉(fclose)连接,以后的访问就直接使用这个连接。具体到代码里就是:先判断 有没有连接,如果有,继续用,如果没有,建立pfsockopen连接。

另外,如果接口返回内容比较短(比如:小于50字符)的话,还有优化的余地,那就是在HTTP请求头的Accept-Encoding中去掉 gzip。它的作用是告诉服务器,我(浏览器)可以接受压缩过的内容,如果服务器也支持gzip就压缩后再返回,浏览器得到内容后解压缩再显示。但是如果 内容太短的话,压缩后体积反而会增加,再加上压缩、解压缩的时间,就更加得不偿失了。

经过以上几步,访问接口速度应该与浏览器一样,理论上还会稍微快一点点。


本文出自 “技术点总结” 博客,请务必保留此出处http://phptianye.blog.51cto.com/10197989/1738500

以上是关于PHP的fsockopen函数详解的主要内容,如果未能解决你的问题,请参考以下文章

使用 fsockopen 时将 php 输出保存到变量中

PHP 使用 fsockopen/cURL 设置浏览器代理

如何使用 fsockopen() 使用密码打开 Telnet 连接?

Nginx 499的问题

PHP异步请求之fsockopen()方法详解

服务器如何开启php的fsockopen函数? 使用发邮箱类