php fsockopen中多线程问题的解决办法[翻译]


Posted in PHP onNovember 09, 2011

问题:
有没有办法在php中实现多线程呢?
假设你正在写一个基于多台服务器的php应用,理想的情况时同时向多台服务器发送请求,而不是一台接一台。
可以实现吗?
回答:
当有人想要实现并发功能时,他们通常会想到用fork或者spawn threads,但是当他们发现php不支持多线程的时候,大概会转换思路去用一些不够好的语言,比如perl。
其实的是大多数情况下,你大可不必使用fork或者线程,并且你会得到比用fork或thread更好的性能。
假设你要建立一个服务来检查正在运行的n台服务器,以确定他们还在正常运转。你可能会写下面这样的代码:

<?php 
$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com"); 
$timeout = 15; $status = array(); 
foreach ($hosts as $host) { 
$errno = 0; 
$errstr = ""; 
$s = fsockopen($host, 80, $errno, $errstr, $timeout); 
if ($s) { 
$status[$host] = "Connectedn"; 
fwrite($s, "HEAD / HTTP/1.0rnHost: $hostrnrn"); 
do { 
$data = fread($s, 8192); 
if (strlen($data) == 0) { break; } 
$status[$host] .= $data; 
} while (true); fclose($s); 
} else { 
$status[$host] = "Connection failed: $errno $errstrn"; 
} 
} 
print_r($status); 
?>

它运行的很好,但是在fsockopen()分析完hostname并且建立一个成功的连接(或者延时$timeout秒)之前,扩充这段代码来管 理大量服务器将耗费很长时间。
因此我们必须放弃这段代码;我们可以建立异步连接-不需要等待fsockopen返回连接状态。PHP仍然需要解析hostname(所以直接使用ip更 加明智),不过将在打开一个连接之后立刻返回,继而我们就可以连接下一台服务器。
有两种方法可以实现;PHP5中可以使用新增的stream_socket_client()函数直接替换掉fsocketopen()。PHP5之前的 版本,你需要自己动手,用sockets扩展解决问题。
下面是PHP5中的解决方法:
<?php 
$hosts = array("host1.sample.com", "host2.sample.com", "host3.sample.com"); 
$timeout = 15; 
$status = array(); 
$sockets = array(); 
foreach ($hosts as $id => $host) { 
$s = stream_socket_client("$host:80", $errno, $errstr, $timeout,STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT); 
if ($s) { 
$sockets[$id] = $s; 
$status[$id] = "in progress"; 
} else { 
$status[$id] = "failed, $errno $errstr"; 
} 
} 
while (count($sockets)) { 
$read = $write = $sockets; 
$n = stream_select($read, $write, $e = null, $timeout); 
if ($n > 0) { 
foreach ($read as $r) { 
$id = array_search($r, $sockets); 
$data = fread($r, 8192); 
if (strlen($data) == 0) { 
if ($status[$id] == "in progress") { 
$status[$id] = "failed to connect"; 
} 
fclose($r); 
unset($sockets[$id]); 
} else { 
$status[$id] .= $data; 
} 
} 
foreach ($write as $w) { 
$id = array_search($w, $sockets); 
fwrite($w, "HEAD / HTTP/1.0rnHost: " . $hosts[$id] . "rnrn"); 
$status[$id] = "waiting for response"; 
} 
} else { 
foreach ($sockets as $id => $s) { 
$status[$id] = "timed out " . $status[$id]; 
} 
break; 
} 
} 
foreach ($hosts as $id => $host) { 
echo "Host: $hostn"; echo "Status: " . $status[$id] . "nn"; 
} 
?>

我们用stream_select()等待sockets打开的连接事件。stream_select()调用系统的select(2)函数来工 作:前面三个参数是你要使用的streams的数组;你可以对其读取,写入和获取异常(分别针对三个参数)。stream_select()可以通过设 置$timeout(秒)参数来等待事件发生-事件发生时,相应的sockets数据将写入你传入的参数。
下面是PHP4.1.0之后版本的实现,如果你已经在编译PHP时包含了sockets(ext/sockets)支持,你可以使用根上面类似的代 码,只是需要将上面的streams/filesystem函数的功能用ext/sockets函数实现。主要的不同在于我们用下面的函数代替 stream_socket_client()来建立连接:
<?php 
// This value is correct for Linux, other systems have other values 
define('EINPROGRESS', 115); 
function non_blocking_connect($host, $port, &$errno, &$errstr, $timeout) { 
$ip = gethostbyname($host); 
$s = socket_create(AF_INET, SOCK_STREAM, 0); 
if (socket_set_nonblock($s)) { 
$r = @socket_connect($s, $ip, $port); 
if ($r || socket_last_error() == EINPROGRESS) { 
$errno = EINPROGRESS; return $s; 
} 
} 
$errno = socket_last_error($s); 
$errstr = socket_strerror($errno); 
socket_close($s); 
return false; 
} 
?>

现在用socket_select()替换掉stream_select(),用socket_read()替换掉fread(),用 socket_write()替换掉fwrite(),用socket_close()替换掉fclose()就可以执行脚本了!
PHP5的先进之处在于,你可以用stream_select()处理几乎所有的stream-例如你可以通过include STDIN用它接收键盘输入并保存进数组,你还可以接收通过proc_open()打开的管道中的数据。
如果你想让PHP4.3.x自身拥有处理streams的功能,我已经为你准备了一个让fsockopen可以异步工作的patch。不赞成使用该补丁, 该补丁不会出现在官方发布的PHP版本中,我在补丁中附带了stream_socket_client()函数的实现,通过它,你可以让你的脚本兼容 PHP5。
PHP 相关文章推荐
用Zend Encode编写开发PHP程序
Oct 09 PHP
四个常见html网页乱码问题及解决办法
Sep 08 PHP
PHP响应post请求上传文件的方法
Dec 17 PHP
Symfony2使用第三方库Upload制作图片上传实例详解
Feb 04 PHP
Laravel使用Caching缓存数据减轻数据库查询压力的方法
Mar 15 PHP
Adnroid 微信内置浏览器清除缓存
Jul 11 PHP
php一个文件搞定微信jssdk配置
Dec 12 PHP
PHP基于mcript扩展实现对称加密功能示例
Feb 21 PHP
PHP实现网站应用微信登录功能详解
Apr 11 PHP
ThinkPHP3.2.3框架Memcache缓存使用方法实例总结
Apr 15 PHP
Laravel框架集成UEditor编辑器的方法图文与实例详解
Apr 17 PHP
php fread函数使用方法总结
May 28 PHP
PHP句法规则详解 入门学习
Nov 09 #PHP
php空间不支持socket但支持curl时recaptcha的用法
Nov 07 #PHP
PHP动态分页函数,PHP开发分页必备啦
Nov 07 #PHP
php获取远程图片的两种 CURL方式和sockets方式获取远程图片
Nov 07 #PHP
php数组函数序列之array_pop() - 删除数组中的最后一个元素
Nov 07 #PHP
php数组函数序列之array_slice() - 在数组中根据条件取出一段值,并返回
Nov 07 #PHP
php数组函数序列之array_unshift() 在数组开头插入一个或多个元素
Nov 07 #PHP
You might like
PHP4在WinXP下IIS和Apache2服务器上的安装实例
2006/10/09 PHP
需要注意的几个PHP漏洞小结
2012/02/05 PHP
php mysql_real_escape_string函数用法与实例教程
2013/09/30 PHP
php数组中删除元素之重新索引的方法
2014/09/16 PHP
PHP实现发送邮件的方法(基于简单邮件发送类)
2015/12/17 PHP
php自定义扩展名获取函数示例
2016/12/12 PHP
详解提高使用Java反射的效率方法
2019/04/29 PHP
javascript十个最常用的自定义函数(中文版)
2009/09/07 Javascript
Jquery中children与find之间的区别详细解析
2013/11/29 Javascript
Javascript小技巧之生成html元素
2014/05/15 Javascript
JavaScript计算某一天是星期几的方法
2015/08/05 Javascript
jQuery实现订单提交页发送短信功能前端处理方法
2016/07/04 Javascript
jquery UI Datepicker时间控件冲突问题解决
2016/12/16 Javascript
微信小程序--组件(swiper)详细介绍
2017/06/13 Javascript
jQuery实现菜单栏导航效果
2017/08/15 jQuery
vue 每次渲染完页面后div的滚动条保持在最底部的方法
2018/03/17 Javascript
Vue 中mixin 的用法详解
2018/04/23 Javascript
Vue.js实现的计算器功能完整示例
2018/07/11 Javascript
vue+express+jwt持久化登录的方法
2019/06/14 Javascript
详解vue中在循环中使用@mouseenter 和 @mouseleave事件闪烁问题解决方法
2020/04/07 Javascript
vue页面跳转实现页面缓存操作
2020/07/22 Javascript
vue 项目引入echarts 添加点击事件操作
2020/09/09 Javascript
python删除过期文件的方法
2015/05/29 Python
Python的Django REST框架中的序列化及请求和返回
2016/04/11 Python
django 按时间范围查询数据库实例代码
2018/02/11 Python
Python turtle画图库&amp;&amp;画姓名实例
2020/01/19 Python
Selenium基于PIL实现拼接滚动截图
2020/04/10 Python
Python实现汇率转换操作
2020/05/03 Python
学生会主席演讲稿
2014/04/25 职场文书
技术员岗位职责
2015/02/04 职场文书
医生辞职信范文
2015/03/02 职场文书
求职自我评价范文
2015/03/09 职场文书
股权投资协议书
2016/03/23 职场文书
MySQL主从搭建(多主一从)的实现思路与步骤
2021/05/13 MySQL
nginx的zabbix 5.0安装部署的方法步骤
2021/07/16 Servers
实战 快速定位MySQL的慢SQL
2022/03/22 MySQL