原始的同时请求多个url,等待每个url返回结果再请求下一个,这样就浪费了时间在等待返回结果上。
失败的尝试:
- 使用fopen打开文件流。结果为fopen这个函数已经等待了返回结果的那段时间,而不是立即返回的。
- 使用fsockopen,但是每打开一个流就读取返回结果。结果,等待返回的这个逻辑就违背了初衷。
成功的尝试:
- 使用fsocketopen同时打开多个文件流,发送http请求。然后再读取这些文件流的结果。
工作流程:
fsocketopen使用非阻塞模式打开多个文件流,并发送http头请求。此时服务器已经与目标建立了多个链接。并已经开始返回数据。接下来,遍历所有文件流,读取返回结果。
测试代码:
<?php
class mhttp{
private $urls = array();
private $streams;
public function addUrl($url){
$this->urls[] = $url;
}
public function request(){
foreach ($this->urls as $url) {
$url = parse_url($url);
$fp = fsockopen($url['host'], 80);
$headers = array();
$headers[] = "GET {$url['path']} HTTP/1.1";
$headers[] = 'Host: localhost';
$headers[] = 'Connection: Close';
$headerStr = '';
foreach ($headers as $header) {
$headerStr .= "{$header}rn";
}
fwrite($fp, "{$headerStr}rn");
$this->streams[] = $fp;
}
sleep(1); //证明发送http请求头后,就马上开始返回结果了
return $this->getResult();
}
private function getResult(){
$result = array();
foreach ($this->streams as $stream) {
$tmp = '';
while (!feof($stream)){
$tmp .= fread($stream, 1024);
}
$result[] = substr($tmp, strpos($tmp, "rnrn") + 4);
fclose($stream);
}
return $result;
}
}
$urls[] = 'http://localhost/mhttp/1.php';
$urls[] = 'http://localhost/mhttp/2.php';
$urls[] = 'http://localhost/mhttp/3.php';
$urls[] = 'http://localhost/mhttp/4.php';
$mhttp = new mhttp();
foreach ($urls as $url){
$mhttp->addUrl($url);
}
var_dump($mhttp->request());
1.php,2.php,3.php,4.php都是类似,只不过输出的是1,2,3,4。
<?php sleep(1); echo 1;
结果
array(4) {
[0] =>
string(1) “1”
[1] =>
string(1) “2”
[2] =>
string(1) “3”
[3] =>
string(1) “4”
}
[Finished in 1.1s]
总耗时是1.1s,在读取返回内容之前的那个sleep(1)如果去掉的话,也同样会是1.1s。这就证明了发送http请求头后目标服务器就已经向本地发送数据了。但,耗时为什么是1.1s呢?因为每个测试文件都sleep了1s。目标服务器在返回前需要执行这个sleep(1)。
写这篇文章是因为看到有人在研究yar,对比测试普通的多次远程请求跟yar的多次远程请求所耗时间。差距不小,所以,用原生的php也写了个简单的并发请求类。当然,这个类还相当不完善,有兴趣的自行完善吧~~~