php中使用fsockopen实现异步请求

php执行一段程序,有可能几毫秒就执行完毕,也有可能耗时较长。例如,用户下单这个事件,如果调用了些第三方服务进行发邮件、短信、推送等通知,可能导致前端一直在等待。而有的时候,我们并不关心这些耗时脚本的返回结果,只要执行就行了。这时候就需要采用异步的方式执行。

众所周知,PHP没有直接支持多线程这种东西。我们可以采用折衷的方式实现。这里主要说的就是fsockopen。

通过fsockopen发送请求并忽略返回结果,程序可以马上返回。

示例代码:

$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET /backend.php HTTP/1.1\r\n";
$out .= "Host: www.example.com\r\n";
$out .= "Connection: Close\r\n\r\n"; fwrite($fp, $out);
/*忽略执行结果
while (!feof($fp)) {
echo fgets($fp, 128);
}*/
fclose($fp);
}

需要注意的是我们需要手动拼出header头信息。通过打开注释部分,可以查看请求返回结果,但这时候又变成同步的了,因为程序会等待返回结果才结束。

实际测试的时候发现,不忽略执行结果,调试的时候每次都会成功发送sock请求;但忽略执行结果,经常看到没有成功发送sock请求。查看nginx日志,发现很多状态码为499的请求。

后来找到了原因:fwrite之后马上执行fclose,nginx会直接返回499,不会把请求转发给php处理。

客户端主动端口请求连接时,NGINX 不会将该请求代理给上游服务(FastCGI PHP 进程),这个时候 access log 中会以 499 记录这个请求。

解决方案:

1)nginx.conf增加配置

# 忽略客户端中断
fastcgi_ignore_client_abort on;

2)fwrite之后使用usleep函数休眠20毫秒:

usleep(20000);

后来测试就没有发现失败的情况了。

附上完整代码:

<?php
/**
* 工具类
* */
class FsockService { public static function post($url, $param){ $host = parse_url($url, PHP_URL_HOST);
$port = 80;
$errno = '';
$errstr = '';
$timeout = 30; $data = http_build_query($param); // create connect
$fp = fsockopen($host, $port, $errno, $errstr, $timeout); if(!$fp){
return false;
} // send request
$out = "POST ${url} HTTP/1.1\r\n";
$out .= "Host:${host}\r\n";
$out .= "Content-type:application/x-www-form-urlencoded\r\n";
$out .= "Content-length:".strlen($data)."\r\n";
$out .= "Connection:close\r\n\r\n";
$out .= "${data}"; fwrite($fp, $out); //忽略执行结果;否则等待返回结果
// if(APP_DEBUG === true){
if(false){
$ret = '';
while (!feof($fp)) {
$ret .= fgets($fp, 128);
}
} usleep(20000); //fwrite之后马上执行fclose,nginx会直接返回499 fclose($fp);
} public static function get($url, $param){
$host = parse_url($url, PHP_URL_HOST);
$port = 80;
$errno = '';
$errstr = '';
$timeout = 30; $url = $url.'?'.http_build_query($param); // create connect
$fp = fsockopen($host, $port, $errno, $errstr, $timeout); if(!$fp){
return false;
} // send request
$out = "GET ${url} HTTP/1.1\r\n";
$out .= "Host:${host}\r\n";
$out .= "Connection:close\r\n\r\n"; fwrite($fp, $out); //忽略执行结果;否则等待返回结果
// if(APP_DEBUG === true){
if(false){
$ret = '';
while (!feof($fp)) {
$ret .= fgets($fp, 128);
}
} usleep(20000); //fwrite之后马上执行fclose,nginx会直接返回499 fclose($fp);
} } ?>

参考:

1、PHP实现异步调用方法研究 | 风雪之隅

http://www.laruence.com/2008/04/14/318.html

2、PHP fsockopen 异步调用接口在nginx上偶尔实效的情况 - 张炜施 - 博客园

http://www.cnblogs.com/zhangweishi/p/5306813.html

3、php 利用fsockopen GET/POST 提交表单及上传文件 - 傲雪星枫 - 博客频道 - CSDN.NET

http://blog.csdn.net/fdipzone/article/details/11712607

4、关于PHP的异步调用 - 浮云比翼 - 博客园

http://www.cnblogs.com/fuyunbiyi/archive/2013/03/27/2985089.html

上一篇:MYSQL查询一周内的数据(最近7天的)


下一篇:PHP fsockopen 异步调用接口在nginx上偶尔实效的情况