使用fastcgi_finish_request实现后台异步处理及提高页面响应速度

 更新时间:2016年11月25日 16:18  点击:1395
fastcgi_finish_request()是冲刷(flush)所有响应的数据给客户端并结束请求。这使得客户端结束连接后,需要大量时间运行的任务能够继续运行。

当PHP运行在FastCGI模式时,PHP FPM提供了一个名为fastcgi_finish_request的方法.按照文档上的说法,此方法可以提高请求的处理速度,如果有些处理可以在页面生成完后再进行,就可以使用这个方法.

听起来可能有些茫然,我们通过几个例子来说明一下:

 代码如下 复制代码
<?php
 
echo '例子:';
fastcgi_finish_request(); /* 响应完成, 关闭连接 */
 
/* 记录日志 */
file_put_contents('log.txt', '生存还是毁灭,这是个问题.');
?>

通过浏览器访问此脚本, 结果发现并没有输出相应的字符串,但却生成了相应的文件.由此说明在调用fastcgi_finish_request后,客户端响应就已经结束,但与此同时服务端脚本却继续运行!

合理利用这个特性可以大大提升用户体验,趁热打铁再来一个例子:

 代码如下 复制代码
<?php
 
echo '例子:';
 
file_put_contents('log.txt', date('Y-m-d H:i:s') . " 上传视频n", FILE_APPEND);
 
fastcgi_finish_request();
 
sleep(1);
file_put_contents('log.txt', date('Y-m-d H:i:s') . " 转换格式n", FILE_APPEND);
 
sleep(1);
file_put_contents('log.txt', date('Y-m-d H:i:s') . " 提取图片n", FILE_APPEND);
 
?>


代码里用sleep模拟一些耗时的操作,浏览时没有被堵塞,程序却都执行了,具体看日志.

末了给您提个醒,Yahoo在Best Practices for Speeding Up Your Web Site中提到了Flush the Buffer Early,也就是利用PHP中的flush方法把内容尽快发到客户端去,它和本文介绍的fastcgi_finish_request有些许的类似.

另外, 从代码的可移植性讲的话, 可以在代码中附上如下代码:

 代码如下 复制代码
if (!function_exists("fastcgi_finish_request")) {
      function fastcgi_finish_request() {
      }
}



不会造成代码部署在非fpm环境下造成问题.

很多站长都兼职卖域名(赚点小外块,不要鄙视),都希望能够在自己的网站上实现域名查询,看看域名是否可以购卖,现在我们就来讲讲用PHP+AJAX如何实现国际域名查询系统。

PHP+AJAX 域名查询预备知识

本查询系统利用 PHP 和 JQUery 的 Ajax 功能实现了对域名信息的查询(这里主要实现了域名是否已经注册的查询)。系统主要用到了万网提供的域名查询 API 接口,相关知识点罗列如下:

    JQUery Ajax 的实现:这部分内容具体可以参见 JQuery API 文档或本站即将推出的《JQUery 教程》。
    file_get_contents 函数:把整个文件读入一个字符串,这里用于读取一个网页(万网 API 返回结果页面)。
    simplexml_load_string 函数:用于解析一个 xml 文档到对象中。
    strrpos 函数:用于定位字符串第一次出现的位置,这里用来搜索关键字。

域名查询系统需求分析

    根据用户输入的域名,查询该域名是否已经被注册。
    对域名注册信息(whois)进行查询,该功能本教程没有实现,可以参考已有功能来实现。

页面/文件信息

    domain.html:表单提交及查询结果信息显示页面。
    domain_check.php:处理查询域名信息的 PHP 文件。

万网域名查询 API 接口

接口采用HTTP,POST,GET协议。

调用URL:http://panda.www.net.cn/cgi-bin/check.cgi

参数名称:area_domain 值为标准域名,例:5idev.com

调用举例:http://panda.www.net.cn/cgi-bin/check.cgi?area_domain=5idev.com

返回XML:

 代码如下 复制代码
<?xml version="1.0" encoding="gb2312"?>
<property>
<returncode>200</returncode>
<key>5idev.com</key>
<original>211 : Domain name is not available</original>
</property>

XML 结果说明:

    returncod:接口调用状态。
    key:表示当前 check 的域名。
    original:域名 check 的结果。

original 有4个结果:

    210 : Domain name is available:表示域名可以注册
    211 : Domain name is not available:表示域名已经被注册
    212 : Domain name is invalid:表示域名参数传输错误
    214 : Unknown error:表示未知错误或查询异常


domain.html 页面关键代码

domain.html 页面实现了表单 Ajax 提交及域名查询结果信息显示。完整的代码如下(注意是utf-8编码):

 

 代码如下 复制代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1
-transitional.dtd">
<html>
<head>
<title>域名注册查询</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="js/jquery-1.4.2.min.js"></script>
<script type="text/javascript">
function domain_check()
{
    var domain = $.trim($("#domain").val());
    if( domain == ''){
        $("#check_result").html('请输入要查询的域名信息,如:5idev.com');
        $("#domain").focus();
        return false;
    }
    $("#domain_result").html('<img src="images/loading.gif" /> 正在查询,请稍后...');
    $.ajax({
        type:"get",
        cache:false,
        datatype: "text",
        url:"domain_check.php?domain="+domain,
        success:function(data){
            $("#domain_result").html(data);
        }
    });
}
</script>
</head>

<body>
<h1>域名注册查询</h1>
<div>
<form >
请输入要查询的域名:www.<input id="domain" type="text" value="5idev.com" onfocus="this.value=''" />
<input type="button" value=" 查 询 " onclick="domain_check()" />
</form>
</div>
<div id="domain_result"></div>

</body>
</html>

 

这里利用 JAuery 实现了 Ajax GET 方式的表单提交,并对输入的表单进行了初步非空检测。
域名查询 PHP 代码

下面是完整的用于查询域名信息和回应 Ajax 请求 PHP 源代码:

 代码如下 复制代码
<?php
$domain = htmlspecialchars(trim($_GET['domain']));
if( !$domain ){
    echo '请输入要查询的域名,如:5idev.com';
    exit;
}
// 调用万网域名查询API
$area_domain = iconv("utf-8", "gb2312",$domain);
$domain_api = 'http://panda.www.net.cn/cgi-bin/check.cgi?area_domain='.$area_domain;
$contents = file_get_contents($domain_api);
$xml = simplexml_load_string($contents);
if (!empty($xml)) {
    switch($xml->original)
    {
        case '210 : Domain name is available':$result = '该域名可以注册';break;
        case '211 : Domain name is not available':$result = '该域名已经被注册';break;
        case '212 : Domain name is invalid':$result = '域名参数错误,请输入的域名格式';break;
        case '214 : Unknown error':$result='查询异常,请稍后再试';break;
    }
} else {
    // 备用,只能查询国际域名
    $url = 'http://www.checkdomain.com/cgi-bin/checkdomain.pl?domain='.$_GET<'domain'>;
    $fp = file_get_contents($url);
    if( strpos($fp, ', has already been registered') ){
        $result = '该域名已经被注册';
    } else {
        $result =  '该域名可以注册';
    }
}
echo '<b>'.$domain.'</b>:'.$result;
?>

需要注意的几个问题

    支持中文域名查询。
    由于域名要在页面显示,因此利用 htmlspecialchars 函数做了特殊 html 代码转换,以防止非法输入,更严格的可以使用正则表达式做检测。
    由于万网接口提供的是 gb2312 编码,因此在这里利用进行了 iconv() 函数进行了编码转换,如果不使用 utf-8 编码则无需转换。
    如果使用 gb2312 编码,在返回 Ajax 结果是可能需要将显示结果进行 gb2312 到 utf-8 编码的转换。
    当万网接口无法返回结果时,启用备用接口进行查询,但只能查询国际域名。

本文我们来讲讲PHP 利用HTTP Status Codes 传递Ajax 成功或失败的状态,文章后面附有PHP实现和C#实例,希望本文对你有所帮助。

一般处理Ajax 回应时会传送的信息种类有:数据、成功信息、错误信息、失败信息以及处理状态,传递的信息种类并不一致,再加上除了数据之外,通常还希望能传递处理状态,这种情况大部分会选择是以JSON 的方式传递这两个信息,以下是常见的几种格式:

但以执行状态跟操作行为作一个归纳,可以区分以下几种传递结果:

{ code: 1, msg: "OK" } { success: true , result: "data" , errorMsg: "" } { status: 'success' , result: [], errorMsg: "" } //...

但以执行状态跟操作行为作一个归纳,可以区分以下几种回传结果:

数据操作 HTTP Method 成功 错误/失败

读取(Read) GET 数据 错误/失败信息

新增 (Create)  POST 成功信息 错误/失败信息
修改 (Update)
删除 (Delete)

从上面的归纳可以看出规律性,接着只要有方法可以传送处理的状态,以及能够区分数据的种类,其实就单纯很多,而HTTP Status Codes 就是用来传递HTTP 的处理状态,如果利用这个方式来传递自定义的处理状态,这样HTTP Content 就可以很单纯传递数据,让数据格式不受限于JSON,还可以使用其他格式(text, xml, html),而且XMLHttpRequest 本身就有处理HTTP Status Codes 的能力,而jQuery.ajax 也有提供error status 的处理,所以可以利用这个来定义状态的处理,在HTTP Status Codes 有几个已经定义状态,很适合用来传递处理状态的信息:

400Bad Request错误的请求适用在表单内容的错误,如必填栏位未填、Email 格式错误

403Forbidden没有权限,被禁止的适用在没有登录或权限不足

500Internal Server Error内部服务器错误适用在程序的错误

jQuery 接收信息的范例

 

 代码如下 复制代码
$.ajax({   
   type: "POST" ,   
   url: document.location,   
   success: function (data, textStatus, jqXHR) {   
       alert(data);   
   },   
   error: function (jqXHR, textStatus, errorThrown) {   
       alert(jqXHR.responseText);   
   }   
}); 

 

PHP 传递错误信息的范例

 

 代码如下 复制代码
if (php_sapi_name() == 'cgi' ){   
   header ( "Status: 400 Bad Request" );   
} else {   
   header ( "HTTP/1.0 400 Bad Request" );   
}   
exit ( "储存失败!!" );  

 

C# MVC 传递错误信息的范例

 

 代码如下 复制代码
Response.TrySkipIisCustomErrors = true ;   
Response.StatusCode = 400;   
return Content( "储存失败!!" );   

 

有时我们为了方便、安全、快速,会把上传的文件单独放一台主机用二级域名访问,但是PHP如何把上传的文件放到另外一台主机呢?这就要跨域跨主机上传了,现在我们用实例来告诉你如何实现。

如何跨网域跨主机跨server上传文件?一般最基本的上传方式是:

1.使用者把文件上传到 web server

2. web server 把上传的文件 利用 move_uploaded_file() 函式,将档案移到指定的文件夹内

但是,有时候我们需要把上传的档案放到另一台专门放文件的 file server,这时候,就无法利用 move_uploaded_file() 去搬移文件了,而需要利用 ftp 去传送文件至 file server,方法很简单...

直接看程式码:

 

 代码如下 复制代码

$file = $_FILES['file'];

$file_tmp = $file['tmp_name'];

$file_name = $file['name'];

if(is_uploaded_file($file_tmp)){ //确定user有"上传"文件

$file_ext = strrchr($file_name,'.'); //上传文件的副文件名

$file_name_new = date('YmdHis').$file_ext;

$host = '127.0.0.1';

$port = '21';

$user = 'admin';

$pass = '123456';

$link = ftp_connect($host,$port);

$login = ftp_login($link,$user,$pass);

ftp_chdir($link,'filedir'); //切换到要放文件的文件夹

if(ftp_put($link,$file_name_new,$file_tmp,FTP_BINARY)){

$msg = '上传成功';

}else{

$msg = '上传失败';

}

}else{

$msg = '上传失败';

}

ftp_close($link);

echo $msg;

 

两地距离我们通常用到最多的就是调用百度地图的api来实现了,但有时我们并不需要这接口了只需要简单的处理一即可,这时可以利用php来实现,具体如下。

目前在做一个交友项目,需要知道两个用户之间的距离。百度了一下,操作如下:
我们最容易获取到用户地理位置的信息就是ip。
我们通过百度api获取用户经纬度,用ip获取经纬度api:

http://developer.baidu.com/map/index.php?title=webapi/ip-api

得到经纬两个用户经纬度之后就可以计算两用户之间的距离了。计算如下:

 代码如下 复制代码
/**
* @desc 根据两点间的经纬度计算距离
* @param float $lat 纬度值
* @param float $lng 经度值
*/
function getDistance($lat1, $lng1, $lat2, $lng2)
{
$earthRadius = 6367000; //approximate radius of earth in meters 
  
 
$lat1 = ($lat1 * pi() ) / 180;
$lng1 = ($lng1 * pi() ) / 180;
 
$lat2 = ($lat2 * pi() ) / 180;
$lng2 = ($lng2 * pi() ) / 180;
 
 
$calcLongitude = $lng2 - $lng1;
$calcLatitude = $lat2 - $lat1;
$stepOne = pow(sin($calcLatitude / 2), 2) + cos($lat1) * cos($lat2) * pow(sin($calcLongitude / 2), 2);
$stepTwo = 2 * asin(min(1, sqrt($stepOne)));
$calculatedDistance = $earthRadius * $stepTwo;
 
return round($calculatedDistance);
}

代码未经测试。测试后再更新本篇文章告知结果。

[!--infotagslink--]

相关文章