浏览器和服务器之间只一种面向无连接的HTTP协议进行通讯的,面向无连接的程序的特点是客户端请求服务端,服务端根据请求输出相应的程序,不能保持持久连接。
这样就出现了一个问题,一个客户端的相应服务端可能执行1秒也有可能执行1分钟,这样浏览器就会一直处于等待状态,如果程序执行缓慢,用户可能就没耐心关掉了浏览器。
而有的时候我们不需要关心程序执行的结果,没有必要这样浪费时间和耐心等待,那我们就要想出办法让程序不收等待在后台静默执行。
比如现在有一个场景,给1000个用户发送一封推荐邮件,用户输入或者导入邮件账号了提交服务器执行发送。
<?php
$count=count($emailarr);
for($i=0;$i<$count;$i++)
{
sendmail(.....);//发送邮件
}
?>
这段代码用户体验极差,也无法实际运用,首先发送这么多邮件会产生服务器运行超时,其实漫长的用户等待时间会让用户对系统产品怀疑和失去信心。但是用户不需要等待到1000封邮件都发送完毕了才提交发送成功,我们完全可以提交后台后直接给用户提示发送成功,然后让后台程序静默依次发送。
这个时候我们就需要“异步执行”技术来执行代码,异步执行的特点是后台静默执行,用户无需等待代码的执行结果,使用异步执行的好处:
1.摆脱了应用程序对单个任务的依赖性
2.提高了程序的执行效率
3.提高了程序的扩展性
4.在一定场景提高了用户体验
5.因为PHP不支持多线程,使用异步调用的请求多个HTTP的方式达到了程序并行执行效果,但是注意的是请求的HTTP过多的话,会大大加大了系统的开销
因此对于耗时的操作适合异步执行,服务器接收到请求后,处理完客户端需要的数据就返回,再异步在服务器执行耗时的操作。
解决方法
fsockopen支持socket编程,可以使用fsockopen实现邮件发送等socket程序等等,使用fcockopen需要自己手动拼接出header部分
可以参考: http://cn.php.net/fsockopen/
使用示例如下:
-
$fp = fsockopen("www.34ways.com", 80, $errno, $errstr, 30);
-
if (!$fp) {
-
echo "$errstr ($errno)<br />\n";
-
} else {
-
$out = "GET /index.php / HTTP/1.1\r\n";
-
$out .= "Host: www.34ways.com\r\n";
-
$out .= "Connection: Close\r\n\r\n";
-
fwrite($fp, $out);
-
/*忽略执行结果
-
while (!feof($fp)) {
-
echo fgets($fp, 128);
-
}*/
-
fclose($fp);
-
}
所以总结来说,fscokopen()函数应该可以满足您的要求。可以尝试一下。
fscokopen的问题和popen一样,并发非常多时会产生很多子进程,当达到apache的连接限制数时,就会挂掉,我问题已经说了这种情况。
PHP本身没有多线程的东西,但可以曲线的办法来造就出同样的效果,比如多进程的方式来达到异步调用,只限于命令模式。还有一种更简单的方式,可用于 Web 程序中,那就是用fsockopen()、fwrite() 来请求一个 URL 而无需等待返回,如果你在那个被请求的页面中做些事情就相当于异步了。
关键代码如下:
复制
- $fp=fsockopen('localhost',80,$errno,$errstr,5);
- if(!$fp){
- echo "$errstr ($errno)<br />\n";
- }
- fputs($fp,"GET another_page.php?flag=1\r\n");
- fclose($fp);
上面的代码向页面 another_page.php 发送完请求就不管了,用不着等待请求页面的响应数据,利用这一点就可以在被请求的页面 another_page.php 中异步的做些事情了。
比如,一个很切实的应用,某个 Blog 在每 Post 了一篇新日志后需要给所有它的订阅者发个邮件通知。如果按照通常的方式就是:
日志写完 -> 点提交按钮 -> 日志插入到数据库 -> 发送邮件通知 ->
告知撰写者发布成功
那么作者在点提交按钮到看到成功提示之间可能会等待很常时间,基本是在等邮件发送的过程,比如连接邮件服务异常、或器缓慢或是订阅者太多。而实际上是不管邮件发送成功与否,保证日志保存成功基本可接受的,所以等待邮件发送的过程是很不经济的,这个过程可异步来执行,并且邮件发送的结果不太关心或以日志形式记录备查。
改进后的流程就是:
日志写完 -> 点提交按钮 -> 日志插入到数据库 --->
告知撰写者发布成功
└ 发送邮件通知 -> [记下日志]
用个实际的程序来测试一下,有两个 php,分别是 write.php 和 sendmail.php,在 sendmail.php 用 sleep(seconds) 来模拟程序执行使用时间。
write.php,执行耗时 1 秒
-
<?php
-
/**
* 耗时异步操作
* @param $url 模板 模块名称/控制器/方法?参数名=参数值
* 严格注意url的格式和fsockopen,fwrite,fclose的格式
* */
-
function asyn_sendmail() {
-
$fp=fsockopen('localhost',80,$errno,$errstr,5);
-
if(!$fp){
-
echo "$errstr ($errno)<br />\n";
-
}
-
sleep(1);
-
$out = "GET /sendmail.php?param=1 HTTP/1.1\r\n";
$out .= "Host:localhost\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
-
fclose($fp);
-
}
-
echo time().'<br>';
-
echo 'call asyn_sendmail<br>';
-
asyn_sendmail();
-
echo time().'<br>';
-
?>
sendmail.php,执行耗时 10 秒
复制
- <?php
- //sendmail();
- //sleep 10 seconds
- sleep(10);
- fopen('C:\'.time(),'w');
- ?>
通过页面访问 write.php,页面输出:
1272472697 call asyn_sendmail
1272472698
并且在 C:\ 生成文件:
1272472708
从上面的结果可知 sendmail.php 花费至少 10 秒,但不会阻塞到 write.php 的继续往下执行,表明这一过程是异步的。