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 相关文章推荐
一个简洁的多级别论坛
Oct 09 PHP
zf框架的Filter过滤器使用示例
Mar 13 PHP
php截取中文字符串函数实例
Feb 23 PHP
PHP获取音频文件的相关信息
Jun 22 PHP
PHP函数实现从一个文本字符串中提取关键字的方法
Jul 01 PHP
WordPress导航菜单的滚动和淡入淡出效果的实现要点
Dec 14 PHP
php实现的一段简单概率相关代码
May 30 PHP
php 输出json及显示json中的中文汉字详解及实例
Nov 09 PHP
php事务回滚简单实现方法示例
Mar 28 PHP
Laravel下生成验证码的类
Nov 15 PHP
php 自定义函数实现将数据 以excel 表格形式导出示例
Nov 13 PHP
PHP CURL实现模拟登陆并上传文件操作示例
Jan 02 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
thinkphp的CURD和查询方式介绍
2013/12/19 PHP
ThinkPHP CURD方法之data方法详解
2014/06/18 PHP
php通过session防url攻击方法
2014/12/10 PHP
PHP实现一个多功能购物网站的案例
2017/09/13 PHP
PHP 实现重载
2021/03/09 PHP
模仿JQuery.extend函数扩展自己对象的js代码
2009/12/09 Javascript
设置iframe的document.designMode后仅Firefox中其body.innerHTML为br
2012/02/27 Javascript
js日期时间补零的小例子
2013/03/05 Javascript
DOM 高级编程
2015/05/06 Javascript
vue vuex vue-rouert后台项目——权限路由(适合初学)
2017/12/29 Javascript
node.js博客项目开发手记
2018/03/16 Javascript
微信小程序实现弹出菜单功能
2018/06/12 Javascript
JS数组方法push()、pop()用法实例分析
2020/01/18 Javascript
微信小程序实现上拉加载功能示例【加载更多数据/触底加载/点击加载更多数据】
2020/05/29 Javascript
JS实现躲避粒子小游戏
2020/06/18 Javascript
Python 3中print函数的使用方法总结
2017/08/08 Python
windows下python安装pip图文教程
2018/05/25 Python
python pygame模块编写飞机大战
2018/11/20 Python
python如何实现word批量转HTML
2020/09/30 Python
python 通过 pybind11 使用Eigen加速代码的步骤
2020/12/07 Python
基于css3 animate制作绚丽的动画效果
2015/11/24 HTML / CSS
HTML5 3D书本翻页动画的实现示例
2019/08/28 HTML / CSS
说一下mysql, oracle等常见数据库的分页实现方案
2012/09/29 面试题
广州品高软件.net笔面试题目
2012/04/18 面试题
儿科主治医生个人求职信
2013/09/23 职场文书
高级工程师岗位职责
2013/12/15 职场文书
大学生如何写自荐信
2014/01/08 职场文书
书法比赛获奖感言
2014/02/10 职场文书
小学新学期寄语
2014/04/02 职场文书
工厂门卫的岗位职责
2014/07/27 职场文书
2015年万圣节活动总结
2015/03/24 职场文书
化工厂员工工作总结
2015/10/15 职场文书
观看《筑梦中国》纪录片心得体会
2016/01/18 职场文书
高端收音机+蓝牙音箱,JBL TUNER FM带收音蓝牙音箱评测
2021/04/24 无线电
详细聊聊关于Mysql联合查询的那些事儿
2021/10/24 MySQL
python套接字socket通信
2022/04/01 Python