RJ博客

PHP fsockopen伪多线程实现异步发送邮件及问题解决

本文目录

每当博客有新评论的时候,我希望系统能及时提醒我去处理。那么评论者在点提交按钮到看到成功提示之间可能会等待很常时间,基本是在等邮件发送的过程。而实际上是不管邮件发送成功与否对评论者来说不是特别重要的信息,这个过程可异步来执行。

然而PHP 本身没有多线程的东西,但可以曲线的办法来造就出同样的效果,比如多进程的方式来达到异步调用,只限于命令模式。另外还有一种更简单的方式可用于 Web 程序中,那就是用 fsockopen()、fputs() 来请求一个 URL, 而无需等待返回,如果你在那个被请求的页面(URL)中做些事情就相当于异步了。

PHP函数:fsockopen简介:打开网络的 Socket 链接。  
语法: 
fsockopen(string hostname, int port, int [errno], string [errstr], int [timeout]);
返回值:资源;
函数种类:网络系统;
内容说明:
hostname及port分别代表网址及端口号。
timeout可省略表示多久没有连上就中断。
该函数返回文件指针,供文件函数使用,包括fgets()、fgetss()、fputs()、fclose()、feof()。
参数errno及errstr可省略,做错误处理使用。
该函数使用阻塞模式(blocking mode)处理,可用set_socket_blocking()转换成无阻塞模式。
备注:fputs()函数写入文件(可安全用于二进制文件)。fputs()函数是fwrite()函数的别名。

下面是两种常用的禁用fsockopen的方法。

1、修改php.ini,将 disable_functions = 后加入 fsockopen

2、修改php.ini,将 allow_url_fopen = On 改为 allow_url_fopen = Off

例子:

//异步线程发送邮件
function asyncTask($id){
    $fp=fsockopen($_SERVER['HTTP_HOST'],80,$errno,$errstr,5);
    if(!$fp){
	    echo "$errstr ($errno)"; 
    }
        
    //注意HTTP/1.1不能少
    $out  = "GET /Show/send.html?id=".$id." HTTP/1.1\r\n"; 
    $out  .= "Host: ".$_SERVER['HTTP_HOST']."\r\n";
    $out .= "Connection: close\r\n\r\n";

	fputs($fp,$out);  //访问请求, 用fwrite也可以
    
    //打印请求结果
    //while(!feof($fp)){
    //    echo fgets($fp, 128);
    //}
    
    //延迟关闭$fp,让nginx将该请求代理给上游服务(FastCGI PHP进程)
    //usleep(1000);
    fclose($fp);
}

这样执行的时候,在Windows+Apache并没有什么问题,但是在Linux+Nginx下就发现不能异步发送邮件了,看了下Nginx的access.log文件,发现http的状态是499(client has closed connection)。

+0800] "GET /Show/send.html?id=109 HTTP/1.1" 499 0 "-" "-" "-"

查了下,发现是用Nginx做 CGI 反代的话,上游后端 PHP 接收不到该请求了,确定问题是因为:fputs之后马上执行fclose,时间太短,NGINX 来不及将该请求代理给上游服务(FastCGI PHP 进程),这个时候 access log 中会以 499 记录这个请求。


要解决这个问题有两种做法:

1.延迟fclose的执行时间 

usleep(1000);

即取消实例代码中的注释,在fclose之前停顿1毫秒。这个设置成1毫秒后还是499,但是能正常发送邮件了。如果设置成

sleep(1);

则状态码为正常的200了 。

(注意:这个时间看程序的实际执行时间调整)


2.将 NGINX FastCGI 忽略客户端中断配置打开

这个可以在nginx.conf主配置文件设置(通用):

fastcgi_ignore_client_abort on;

也可以单独的web站点.conf里面单独配置(只针对某一站点使用):

location ~ .*\.php$
{
    fastcgi_ignore_client_abort on;
    #proxy_ignore_client_abort on; #针对其他类型的反代,PHP的场景不适用
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include     fastcgi_params;
}

这样无论客户端是否断开,都会将这个请求代理给上游,并且会记录上游服务处理后的返回状态。

另外,还有一个类似配置 proxy_ignore_client_abort on; 这个配置是针对其他类型的反代,PHP 的场景并不适用。




References:

http://www.phpchina.com/blog-52440-195270.html

http://blog.csdn.net/chadxia88_go/article/details/51774106

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

https://hacpai.com/article/1444367397136?m=0

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




相关推荐

发表评论