原始的同时请求多个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也写了个简单的并发请求类。当然,这个类还相当不完善,有兴趣的自行完善吧~~~