当前位置: 首页 > 图文教程 > 网络编程 > PHP > PHP web开发HTTP协议中的KeepAlive

PHP
《PHP设计模式介绍》第十三章 适配器模式
《PHP设计模式介绍》第十四章 动态记录模式
《PHP设计模式介绍》第十五章 表数据网关模式
《PHP设计模式介绍》第十六章 数据映射模式
《PHP设计模式介绍》第十七章 MVC 模式
Zend Framework 入门——快速上手
Zend Framework 入门——多国语言支持
Zend Framework 入门——错误处理
Zend Framework 入门——页面布局
详细介绍php5编程中的异常处理
PHP5 OOP编程中的代理与异常
PHP程序的常见漏洞攻击分析
PHP.MVC的模板标签系统
PHP教程:PHP编码书写规范
PHP开发大型项目的方法:OOP思想
php使用curl模拟用户登陆
php对gb编码动态转utf-8编码的几种方法评测
php设计模式介绍之章代理模式
“在phpMyAdmin使用用户口令登陆”补充
PHP入门速成

PHP web开发HTTP协议中的KeepAlive


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-09-28   浏览: 90 ::
收藏到网摘: n/a

这篇文章已经写完将近一年了,最近从历史邮件里面翻出来,和大家分享一下。

其中使用PHP实现持久的HTTP连接,让我费了很多心思。

曾经想过使用C语言编写一个PHP的扩展来实现,后来发现pfsockopen这个函数,让我豁然开朗,避免重新发明一个轮子,呵呵。

一,KeepAlive的概念:

参见 http://en.wikipedia.org/wiki/HTTP_persistent_connection

二,KeepAlive的客户端实现:

使用了PHP支持的 pfsockopen 来实现,参见:http://cn.php.net/pfsockopen

KeepAlive必要的Header有:

Connection: Keep-Alive
Content-Length: xxx

三,性能对比测试:

几种对比实现方式:

1,使用fsockopen来实现,读取body内容后,关闭连接,参见测试程序中的ohttp_get实现。
2,使用pfsockopen来实现,读取body内容后,不关闭连接,参见测试程序中的phttp_get实现。
3,php实现的file_get_contents
4,第三方测试工具ab

前三种测试在测试程序中都包含了。

测试用例 一:

前三种php实现的客户端单进程单线程请求lighttpd服务器一个16字节的静态文件。顺序请求10000次。
客户端与服务器部署在不同服务器,通过内网请求。

测试结果:

第一次:

[root@localhost ~]# /opt/bin/php tp.php
phttp_get: 5.3641529083252
ohttp_get: 8.1628580093384
file_get_contents: 12.217950105667

第二次:

[root@localhost ~]# /opt/bin/php tp.php
phttp_get: 5.033059835434
ohttp_get: 9.589075088501
file_get_contents: 12.775387048721

第三次:

[root@localhost ~]# /opt/bin/php tp.php
phttp_get: 5.0181269645691
ohttp_get: 8.2286441326141
file_get_contents: 11.089616060257

测试用例 二:

使用第三方工具ab来进行测试,-k参数开打开keepalive支持,不做并发测试,顺序请求10000次。
客户端与服务器部署在不同服务器,通过内网请求。

以下测试结果部分省略:

未打开keepalive:

[root@localhost ~]# ab -n 10000 -c 1 “http://10.69.2.206:8080/sms/ns2/save_msg.txt”

Finished 10000 requests

Concurrency Level: 1
Time taken for tests: 10.410467 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Total transferred: 2480000 bytes
HTML transferred: 160000 bytes
Requests per second: 960.57 [#/sec] (mean)
Time per request: 1.041 [ms] (mean)
Time per request: 1.041 [ms] (mean, across all concurrent requests)
Transfer rate: 232.55 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 30.0 0 3002
Processing: 0 0 0.4 0 9
Waiting: 0 0 0.3 0 9
Total: 0 0 30.0 0 3003

打开keepalive:

[root@localhost ~]# ab -k -n 10000 -c 1 “http://10.69.2.206:8080/sms/ns2/save_msg.txt”

Finished 10000 requests

Concurrency Level: 1
Time taken for tests: 4.148619 seconds
Complete requests: 10000
Failed requests: 0
Write errors: 0
Keep-Alive requests: 9412
Total transferred: 2527060 bytes
HTML transferred: 160000 bytes
Requests per second: 2410.44 [#/sec] (mean)
Time per request: 0.415 [ms] (mean)
Time per request: 0.415 [ms] (mean, across all concurrent requests)
Transfer rate: 594.66 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 5
Processing: 0 0 2.1 0 203
Waiting: 0 0 2.1 0 203
Total: 0 0 2.1 0 203

四,在实际中的应用

以上实现的phttp_get和mysql memcache的 中的“保持连接”概念类似,这种技术一般来说,只适用于fastcgi模式的web服务器。
对于本机之间的http通信,在测试过程中发现phttp_get的优势有限,基本合乎逻辑。
对于本身处理时间比较长的服务,phttp_get的优势也不明显。
综上,phttp_get适用于fastcgi模式的web应用调用远程http服务,且此http服务器响应时间比较短的情况。

五,服务端需要注意的事项

1,http服务器必须支持HTTP/1.1协议
2,php应用必须返回Content-Length:的header,具体实现参见:

http://cn.php.net/manual/en/function.ob-get-length.php

需要在代码中加入:

ob_start();
$size=ob_get_length();
header(”Content-Length: $size”);
ob_end_flush();

最后附上测试代码:

<?php

//$url=http://10.69.2.206:8080/sms/ns2/save_msg.txt

function ohttp_get($host,$port,$query,&$body)
{
$fp=pfsockopen($host,$port,$errno,$errstr,1);
if(!$fp)
{
var_dump($errno,$errstr);
return -1;
}
$out = “GET ${query} HTTP/1.1\r\n”;
$out.= “Host: ${host}\r\n”;
$out.= “Connection: close\r\n”;
$out.= “\r\n”;
fwrite($fp,$out);
$line=trim(fgets($fp));
$header.=$line;
list($proto,$rcode,$result)=explode(” “,$line);
$len=-1;
while( ($line=trim(fgets($fp))) != “” )
{
$header.=$line;
if(strstr($line,”Content-Length:”))
{
list($cl,$len)=explode(” “,$line);
}
if(strstr($line,”Connection: close”))
{
$close=true;
}
}
if($len < 0)
{
echo “ohttp_get must cope with Content-Length header!\n”;
return -1;
}
$body=fread($fp,$len);
if($close)
fclose($fp);
return $rcode;
}
function phttp_get($host,$port,$query,&$body)
{
$fp=pfsockopen($host,$port,$errno,$errstr,1);
if(!$fp)
{
var_dump($errno,$errstr);
return -1;
}
$out = “GET ${query} HTTP/1.1\r\n”;
$out.= “Host: ${host}\r\n”;
$out.= “Connection: Keep-Alive\r\n”;
$out.= “\r\n”;
fwrite($fp,$out);
$line=trim(fgets($fp));
$header.=$line;
list($proto,$rcode,$result)=explode(” “,$line);
$len=-1;
while( ($line=trim(fgets($fp))) != “” )
{
$header.=$line;
if(strstr($line,”Content-Length:”))
{
list($cl,$len)=explode(” “,$line);
}
if(strstr($line,”Connection: close”))
{
$close=true;
}
}
if($len < 0)
{
echo “phttp_get must cope with Content-Length header!\n”;
return -1;
}
$body=fread($fp,$len);
if($close)
fclose($fp);
return $rcode;
}

$time1=microtime(true);
for($i=0;$i<10000;$i++)
{
$host=”10.69.2.206″;
$port=8080;
$query=”/sms/ns2/save_msg.txt”;
$body=”";
$r=ohttp_get($host,$port,$query,$body);
if($r != 200)
{
echo “return code : $r\n”;
}
}
$time2=microtime(true);
for($i=0;$i<10000;$i++)
{
$url=”http://10.69.2.206:8080/sms/ns2/save_msg.txt”;
$host=”10.69.2.206″;
$port=8080;
$query=”/sms/ns2/save_msg.txt”;
$body=”";
$r=phttp_get($host,$port,$query,$body);
if($r != 200)
{
echo “return code : $r\n”;
}
}
$time3=microtime(true);
for($i=0;$i array( ‘timeout’ => 1 )
)
);
$body=file_get_contents($url, 0, $ctx);
$r=200;
if($r != 200)
{
echo “return code : $r\n”;
}
}
$time4=microtime(true);

echo “phttp_get: “.($time3-$time2).”\n”;
echo “ohttp_get: “.($time2-$time1).”\n”;
echo “file_get_contents: “.($time4-$time3).”\n”;

?>