4 回复

关于libevent扩展升级php7遇到的问题

youwuku 回复了问题 • 3 人关注 • 2020-09-07 17:28 • 来自相关话题

1 回复

workerman 4.0.8版本 在nfs文件系统下的问题

walkor 回复了问题 • 2 人关注 • 2020-08-31 15:41 • 来自相关话题

2 回复

我被多线程逼疯了,我要发一次灌水贴,我不管,我要发

半壶水 回复了问题 • 3 人关注 • 2020-08-18 16:19 • 来自相关话题

1 回复

webman中 illuminate/database模型问题

walkor 回复了问题 • 3 人关注 • 2020-07-27 17:04 • 来自相关话题

10 回复

swoole和workerman性能对比

张先生 回复了问题 • 12 人关注 • 2020-06-26 22:13 • 来自相关话题

2 回复

nodemon监听文件变化,并重启项目webman

dazhaozhao 发表了文章 • 2 个评论 • 184 次浏览 • 2020-06-19 10:09 • 来自相关话题

最近玩node.js发现了一个第三方模块,nodemon,挺有意思的,能监听文件变化,并自动重启web服务,就想着能不能用在webman上,就不用手动重启了,发现了老外写的一篇文章,动手试验了下,居然可以,非常惊喜,大伙可以试试结合着来,加快开发速度。链接放下 ...查看全部

最近玩node.js发现了一个第三方模块,nodemon,挺有意思的,能监听文件变化,并自动重启web服务,就想着能不能用在webman上,就不用手动重启了,发现了老外写的一篇文章,动手试验了下,居然可以,非常惊喜,大伙可以试试结合着来,加快开发速度。链接放下边:


https://sergeyzhuk.me/2019/09/16/live-reload-php-applications/

5 回复

小白使用workman指南

独孤紫龙 发表了文章 • 5 个评论 • 331 次浏览 • 2020-06-08 15:21 • 来自相关话题

项目背景: 自己做的项目(基于TP5框架)需要个语音通知系统,只要我的后台接收到了信息,我就发送消息给微信小程序,于是就用到了workman。 使用工具: TP5(thinkphp5)+Workerman 整体思 ...查看全部

项目背景:


自己做的项目(基于TP5框架)需要个语音通知系统,只要我的后台接收到了信息,我就发送消息给微信小程序,于是就用到了workman。


使用工具:


TP5(thinkphp5)+Workerman


整体思路:


用workman的soket链接,当项目需要发消息的时候,发送给小程序,然后小程序根据传过来的数据做逻辑判断选择是否播报语音。


实现过程:


1、安装workman
(对于composer工具,作为标准小白是一直不会用的。其实用很简单,打开命令行工具,就是电脑自带的命令行,定位到自己的项目文件夹里去,然后按照workman文档里的命令粘贴进去,就可以安装。我的服务器是阿里云linux系统的,所以我执行的是以下两条命令。因为每个人的服务器都不一样,规则不同,所以执行的情况就不一样。我也遇到了错误,基本上都能百度到原因,差不多就是版本不匹配的原因。)


命令:阿里云下composer安装,先切换镜像:


composer config repo.packagist composer https://mirrors.aliyun.com/composer/


安装命令:


composer require topthink/think-worker=1.0.*


注意自己TP5的版本哈。以下是我个人理解的小白总结,如果有不对,大佬指正哈:


接下来根据版本选择composer:


tp5.0-worker1.0
tp5.1-worker2.0
tp6-worker3.o


当你看到自己项目里vendor文件夹里多出来俩文件夹:topthink和workman,就是成功了。


2、启动workman


在项目的根目录,也就是和application同级的目录写一个启动文件,通过命令行执行。


<?php
// +----------------------------------------------------------------------
// | Workerman启动页
// | Author: Worker fan
// +----------------------------------------------------------------------

define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','worker/Worker');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';

这个文件随便命名,我的叫service.php


然后构建服务器的处理消息模块,按照上面文件写的规则建立控制器。


<?php
/*
workerman的控制器
Author:workerfan
*/

namespace app\worker\controller;
use think\worker\Server;

class Worker extends Server
{
protected $socket = 'websocket://0.0.0.0:9383';

/**
* 收到信息
* @param $connection
* @param $data
*/
public function onMessage($connection, $data)
{
$connection->send('我收到你的信息了');
}

/**
* 当连接建立时触发的回调函数
* @param $connection
*/
public function onConnect($connection)
{

}

/**
* 当连接断开时触发的回调函数
* @param $connection
*/
public function onClose($connection)
{

}

/**
* 当客户端的连接上发生错误时触发
* @param $connection
* @param $code
* @param $msg
*/
public function onError($connection, $code, $msg)
{
echo "error $code $msg\n";
}

/**
* 每个进程启动
* @param $worker
*/
public function onWorkerStart($worker)
{

}
}

上面是个文档里的代码,我对于workerman的需求比较简单,所以就用了最精简的控制器。因为是小程序通信,所以监听的是:0.0.0.0,如果是项目内通信,可以监听:127.0.0.1。端口自己随便定义,只要不被占用就行。


写好上面这些文件,就可以启动了。


启动命令两种:


php service.php start (这种启动会随着终端断开而断开)


php service.php start -d (这种启动就会一直保持在线,除非手动关停,所以叫守护进程启动,其实字面理解就能理解。)


一般都是第二种命令启动。


3、微信小程序链接workman


这一步费时挺多,因为啥都不懂,所以很狂暴。


其实都是挺简单的问题,就是小白啥都不懂,所以得一点点自己慢慢找线索。


因为每一个小白的服务器配置都不一样,所以遇到的问题也五花八门因此也没有固定回答。


这里我就说两个可能是最通用的小白问题吧。


首先是微信小程序的代码,很简单:


//连接socket服务器
wx.connectSocket({
  url: 'wss://www.*************.com:443/wss',
success:(function(){
console.log("发送请求");
})  
});  
//socket打开时执行的事情
wx.onSocketOpen(function(res) { 
   wx.sendSocketMessage('Hellow,service');  
   console.log("给服务器发一个问候");  
});
wx.onSocketError(function(errMsg){
console.log("错误:".errMsg);
})
//服务器给用户发信息触发的事件          
wx.onSocketMessage(function(res) {  
  console.log("收到服务端的消息:" + res.data);  
});  

首先微信小程序socket是只能和https进行通讯的,所以网站必须是https的。配置https网站如果你用的是宝塔的管理平台就非常简单了,只需要到自己网站下找到ssl配置把证书给粘贴进去就行。然后把workman里关于转发的服务器配置给粘贴到自己的服务器配置上去就可以了。


这里会有俩常见问题。


1)端口没有放行,会有连接超时的警告或者错误。


宝塔的管理虽然也可以放行端口,但是貌似不是绝对,到阿里云的控制台的安全规则里面没有发现端口被放行,于是总是超时。


所以workman开了的时候,要先用命令测试下,自己的端口能不能访问到。


2) 客户端访问地址的书写错误,会导致ssl协议不正确的错误。


因为对配置文件的内容有误解,一开始访问的地址写的都是自己配置的端口号。后来知道了原理,nginx服务器监听的是443端口,自己修改的端口是从443转发到上面去的。所以url地址应该是:


wss://****.com:443/wss


而不是wss://****.com:3344/wss


这点很重要,估计能解决大把小白的疑惑。


做完以上这些,微信小程序就能成功和workman通信了,nice.


总结:


workman是一个很棒的php框架,就和它介绍自己一样,填充了php关于socket的很多空白,相信它将来一定会越来越好。对于我们这类的小白,因为基础知识的欠缺,所以有些答案就没有真正解决问题。其实,控制台回馈的错误就很好的告诉了问题的所在,只要静下心来一步步地根据错误去解决问题,最终都会有一个正确的答案的。以上。就是作为小白总结的全部内容了,希望能对别人有所帮助。


最后,感谢workman!

2 回复

thinkphp5整合workerman,tp5整合workerman,使用原汁原味的workerman

回复

a392223903 回复了问题 • 2 人关注 • 2020-04-17 22:52 • 来自相关话题

1 回复

做了一个windows下监控文件修改自动重载workerman的工具

damao 回复了问题 • 2 人关注 • 2020-03-20 10:50 • 来自相关话题

1 回复

用workerman代替redis

six 回复了问题 • 3 人关注 • 2020-02-27 10:03 • 来自相关话题

1 回复

jsonRPC调用加载动态对象

walkor 回复了问题 • 2 人关注 • 2020-02-06 17:15 • 来自相关话题

1 回复

实测GatewayWorker性能问题

智佳思远 回复了问题 • 2 人关注 • 2020-01-05 15:00 • 来自相关话题

3 回复

让windows开发支持代码热更新,workerman-filemonitor for windows

lxping 发表了文章 • 3 个评论 • 478 次浏览 • 2019-12-31 02:05 • 来自相关话题

workerman-filemonitor只能用于linux,但是我们平时开发都是用的windows。基于workerman-filemonitor二次修改,可完美实现windwos开发时的代码热更新。 经过测试,windows下不能直接在file ...查看全部

workerman-filemonitor只能用于linux,但是我们平时开发都是用的windows。基于workerman-filemonitor二次修改,可完美实现windwos开发时的代码热更新。


经过测试,windows下不能直接在filemonitor启动的进程中获取我们需要的父进程,所以需要在你启动的gateway文件中添加如下代码(该代码一定要放在Worker::runAll()之前)


require_once __DIR__ . '/FileMonitor.php';
new FileMonitor($web, 'www', 5);

FileMonitor.php


<?php

use \Workerman\Worker;
use \Workerman\Lib\Timer;

/**
* workerman-filemonitor for windows
*
* 监控文件更新并自动reload workerman
*
* 使用方法:
* require_once __DIR__ . '/FileMonitor.php';
* new FileMonitor($worker, $dir, $timer);
*/
class FileMonitor
{
//待监听的项目目录
private $_monitor_dir = '';

//热更新间隔时间,默认3s
private $_interval = 0;

//最后一次同步时间
private $_last_time = 0;

function __construct ($worker, $dir, $timer = 3)
{
// watch Applications catalogue
$this->_monitor_dir = __DIR__ .'/'. $dir;

$this->_interval = $timer;

$this->_last_time = time();

// Emitted when data received
$worker->reloadable = false;

// Emitted when data received
$worker->onWorkerStart = function()
{
// watch files only in daemon mode
if (Worker::$daemonize === false)
{
// chek mtime of files per second
Timer::add($this->_interval, [$this, 'monitor']);
}
};
}

//监听器,kill进程
public function monitor ()
{
// recursive traversal directory
$iterator = new RecursiveDirectoryIterator($this->_monitor_dir);
$iterator = new RecursiveIteratorIterator($iterator);

foreach ($iterator as $file)
{
// only check php files
if (pathinfo($file, PATHINFO_EXTENSION) != 'php') continue;

// check mtime
if ($this->_last_time < $file->getMTime())
{
exec('taskkill -f -pid '. getmypid());
$this->_last_time = $file->getMTime();
return true;
}
}
}
}
0 回复

让start_for_win.bat自动识别start文件

lxping 发表了文章 • 0 个评论 • 391 次浏览 • 2019-12-31 01:55 • 来自相关话题

把gatewayworker示例文件start_for_win.bat替换为如下代码,即可自动识别项目中的start_开头的php文件,仅限windows。 代码中的YourApp为你的项目文件夹 @echo off set ...查看全部

把gatewayworker示例文件start_for_win.bat替换为如下代码,即可自动识别项目中的start_开头的php文件,仅限windows。


代码中的YourApp为你的项目文件夹


@echo off
setlocal enabledelayedexpansion
set app=Applications\YourApp
for /f "delims=" %%i in ('dir %app%\start_*.php /b') do (
set FILES=!FILES! %app%\%%i
)
php !FILES!
pause
5 回复

16进制字符串转换为32位单精度浮点数

flyingfish 发表了文章 • 5 个评论 • 1024 次浏览 • 2019-08-14 16:52 • 来自相关话题

日前,想用gatewayworker接收设备发过来的信息,但是发现php中没有直接将16进制字符串转换为32位单精度浮点数的函数,在网上查了许久,查到了这个 [url]http://www.zhanglirong.cn/article/index/cid/1/ ...查看全部

日前,想用gatewayworker接收设备发过来的信息,但是发现php中没有直接将16进制字符串转换为32位单精度浮点数的函数,在网上查了许久,查到了这个 [url]http://www.zhanglirong.cn/article/index/cid/1/id/69.html。[/url]
[code]$a = '4145C28F';
$v = hexdec($a);
$x = ($v & ((1 << 23) - 1)) + (1 << 23)  ($v >> 31 | 1);
$exp = ($v >> 23 & 0xFF) - 127;
$res = $x 
 pow(2, $exp - 23);
if(($v >> 31) == 1){
    $res = -$res;
}
echo $res;[/code]
对这部分代码的理解的前提是需要了解ieee 754标准中浮点数表示法:[url]http://c.biancheng.net/view/314.html[/url]
$v = hexdec($a) ;将字符串$a放到一个32bit的二进制串里:‭01000001010001011100001010001111‬
$x = ($v & ((1 << 23) - 1)) + (1 << 23) ($v >> 31 | 1);取出尾数,并在最左边添加1。
$exp = ($v >> 23 & 0xFF) - 127; 获取阶码。
$res = $x
pow(2, $exp - 23); 根据阶码移动小数点的位置,我觉得这里理解起来比较费劲,我的理解是$x可以理解为一个整数,pow(2,$exp-23)为该整数需要缩小的倍数。比如对于十进制数1000.00来说,缩小两倍,那么小数点就往左移两位。
后面用于判断符号的代码可用a?b:c三目运算符更简洁。
 

1 回复

php毫秒定时器,基于event扩展

six 回复了问题 • 3 人关注 • 2019-08-14 14:33 • 来自相关话题

4 回复

php毫秒定时器,也支持win

dazhaozhao 回复了问题 • 5 人关注 • 2019-08-13 14:52 • 来自相关话题

6 回复

php终极数据缓存,比redis、GlobalData等快200倍以上,极致性能

dingfei 发表了文章 • 6 个评论 • 2020 次浏览 • 2019-05-16 21:19 • 来自相关话题

一、效果:每秒读取2000万条。写入2200万条。cpu开销很小二、原理:1.将数据以数组方式存储在内存中,php进程需要数据时直接通过内存地址访问数据,没有任何IO开销以及CPU开销。 三、具体实现:1.利用linux的写时复制技术。运行php主 ...查看全部

一、效果:每秒读取2000万条。写入2200万条。cpu开销很小二、原理:1.将数据以数组方式存储在内存中,php进程需要数据时直接通过内存地址访问数据,没有任何IO开销以及CPU开销。
三、具体实现:1.利用linux的写时复制技术。运行php主进程一次性读取所有的数据保存到数组中,然后通过workerman监听端口。每次收到请求时:创建一个子进程去执行任务,执行完成之后子进程自动结束。
      2.子进程完全共享主进程的数组,不消耗内存。
四、代码[code] ini_set('memory_limit','5872M'); // 临时设置最大内存占用为5G
$a = [[[[]]]];
$start = microtime(true);
for ($i=0; $i<=200; $i++) {
for ($j=0; $j<=100; $j++) {
for ($k=0; $k<=1000; $k++) {
$a[$i][$j][$k] = '我是一条数据';
}
}
}
// 2000万数据,时间1秒,消耗内存720M。 相当于1000万-0.5秒-360M
echo microtime(true) - $start, "s。主进程写入完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB
";
$start = microtime(true);
for ($i=0; $i<=200; $i++) {
for ($j=0; $j<=100; $j++) {
for ($k=0; $k<=1000; $k++) {
$m = $a[$i][$j][$k] ;
}
}
}
echo microtime(true) - $start, "s。主进程读取完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB
";
$start = microtime(true);
$intNum= 1; // 进程总数
$pids= array(); // 进程PID数组
for($i= 0; $i<$intNum; $i++)
{
$pids[$i] = pcntl_fork();// 产生子进程,而且从当前行之下开试运行代码,而且继承父进程的所有变量
if($pids[$i] == -1){echo"couldn't fork". "\n";
}elseif(!$pids[$i]){
$m= 0;
$start = microtime(true);
for ($i=0; $i<=200; $i++) {
for ($j=0; $j<=100; $j++) {
for ($k=0; $k<=1000; $k++) {
$m = $a[$i][$j][$k] ;
}
}
}
echo microtime(true) - $start, "s。子进程读取完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB
";


            sleep(100);
exit(0);//子进程要exit否则会进行递归多进程,父进程不要exit否则终止多进程
}
}
echo microtime(true) - $start, "s。子进程创建完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB

";
sleep(200);[/code]
五:代码运行结果:
1.0883929729462s。主进程写入完成,当前内存:719.90679168701MB
0.66096806526184s。主进程读取完成,当前内存:719.90679168701MB
0.0097589492797852s。子进程创建完成,当前内存:719.90715026855MB
0.72923994064331s。子进程读取完成,当前内存:719.90715026855MB

0 回复

GatewayWorker分布式部署时的Gateway连接失败解决办法

caiqy 发表了文章 • 0 个评论 • 843 次浏览 • 2019-05-16 10:26 • 来自相关话题

问题场景: 分布式部署gateway时,businessworker频繁从gateway查询数据(如 getSession, getOnlinexxx, getxxx),当用户量上升到一定程度会出现unable to connect to tcp: ...查看全部

问题场景:
分布式部署gateway时,businessworker频繁从gateway查询数据(如 getSession, getOnlinexxx, getxxx),当用户量上升到一定程度会出现unable to connect to tcp://xxxx 或者 can not to conect to tcp://xxxx,导致用户请求未处理,如果未在外层捕获异常会导致businessworker进程重启
 
原因猜测:
getxxx在Lib/Gateway.php中都是用的短链接,每一次请求都会创建新的连接,两台服务器之间建立连接频率过快 
解决办法:
起初发现netstats中存在几千个连接gateway内部端口的TIME_WAIT连接,就加大了TIME_WAIT最大容量,开启了tcp_tw_reuse,用户量上去后,TIME_WAIT不多了,但还是出现了上面的问题
最终通过修改Lib/Gateway.php的代码,使用长连接解决了问题
 
[img]https://wenda.workerman.net/uploads/article/20190516/90x90_a9fb60bce3674f09d626dfd8ea180b04.png[/img]
启动文件中增加如下代码
[code]Lib\Gateway::$persistentConnection = true;[/code]

0 回复

基于GatewayWorker+Vue所写的聊天室

回复

搬砖屌丝 发起了问题 • 2 人关注 • 2018-12-07 14:05 • 来自相关话题

0 回复

Workerman MySQL 连接池

stephen 发表了文章 • 0 个评论 • 2766 次浏览 • 2018-11-22 16:54 • 来自相关话题

由于 workerman 的mysql组件在操作事物的时候需要在一个独立的db连接里面处理,如果相同的连接处理一个事物可能会造成异常,所以开发了一个db连接池,代码如下,如果有问题,欢迎交流指教   <?php ...查看全部

由于 workerman 的mysql组件在操作事物的时候需要在一个独立的db连接里面处理,如果相同的连接处理一个事物可能会造成异常,所以开发了一个db连接池,代码如下,如果有问题,欢迎交流指教
 


<?php

use Workerman\MySQL\Connection as DbConn;

class DbPool
{

/**
* db connection pool
* @var resource
*/
public static $_db = null;

/**
* db connection pool
* @var array
*/
public static $_dbs = [];

/**
* db mapping to pool
*/
public static $_dbsPool = [];

/**
* 闲置分配和设置锁
* @var boolean
*/
public static $_idleLock = false;

/**
* 连接池默认数量
* @var int
*/
public static $_defaultDbConnPoolNum = 100;

/**
* make a db connection
* @
*/
public static function initDbConn()
{
if (self::$_db === null) {
self::$_db = self::createDbConn();
}
return self::$_db;
}

/**
* Create a new db connection
* @return db instance
*/
public static function createDbConn()
{
$config = $GLOBALS['app_conf']['db'];
return new DbConn(
$config['host'],
$config['port'],
$config['username'],
$config['password'],
$config['db_name']
);
}

/**
* Get a DB connection instance
* @param mixed $useTrans Defaults to false
* @return object The db connection instance
*/
public static function getDB($useTrans = false)
{
if ($useTrans === false) {
return self::initDbConn();
}

if (!isset(self::$_dbsPool[$useTrans])) {
$index = self::getIdle($useTrans); // 获取置的连接,如果有,用闲置
if ($index === false || !isset(self::$_dbs[$index])) {
$index = 'dbConn_' . md5(microtime(true) . count(self::$_dbs));
self::$_dbs[$index] = self::createDbConn();
}
self::$_dbsPool[$useTrans] = $index;
} else {
$index = self::$_dbsPool[$useTrans];
}
return self::$_dbs[$index];
}

/**
* close db conn
* @param mixed $useTrans defaults to false
*/
public static function closeDB($useTrans = false)
{
if ($useTrans !== false and isset(self::$_dbsPool[$useTrans])) {
if (count(self::$_dbs) > self::$_defaultDbConnPoolNum) {
$index = self::$_dbsPool[$useTrans];
self::$_dbs[$index]->closeConnection();
self::$_dbs[$index] = null;
unset(self::$_dbs[$index]);
unset(self::$_dbsPool[$useTrans]);
} else {
self::setIdle($useTrans); // 将连接设置为闲置
}
}
if ($useTrans === false) {
self::$_db = null;
}
}

/**
* 从pool获取一个闲置的连接, 并赋值为指定的连接transToken
* @return mixed 找到闲置则返回连接索引,否则返回false
*/
private static function getIdle($transToken)
{
if (self::$_idleLock === true) {
return;
}
self::$_idleLock = true;
foreach (self::$_dbsPool as $key => $item) {
if (strpos($key, 'idle_') === 0) {
self::$_dbsPool[$transToken] = self::$_dbsPool[$key];
unset(self::$_dbsPool[$key]);
self::$_idleLock = false;
return self::$_dbsPool[$transToken];
}
}
self::$_idleLock = false;
return false;
}

/**
* 将一个连接设置为闲置
*/
private static function setIdle($transToken)
{
if (self::$_idleLock === true) {
return;
}
self::$_idleLock = true;
if (isset(self::$_dbsPool[$transToken])) {
$key = 'idle_' . md5(microtime(true));
$tmp = self::$_dbsPool[$transToken];
unset(self::$_dbsPool[$transToken]);
self::$_dbsPool[$key] = $tmp;
}
self::$_idleLock = false;
}
}
0 回复

windows phpstudy配置环境变量

回复

lalala 发起了问题 • 2 人关注 • 2018-11-12 09:49 • 来自相关话题

2 回复

感觉在runAll()之前实例化mysql或者redis是可以的,因为是copy on write

walkor 回复了问题 • 2 人关注 • 2018-11-11 10:53 • 来自相关话题

0 回复

多卓科技——物联智能手环

回复

智骏科技 发起了问题 • 1 人关注 • 2018-11-08 18:10 • 来自相关话题

1 回复

centos php7.1环境安装event扩展问题

walkor 回复了问题 • 2 人关注 • 2018-10-15 15:42 • 来自相关话题

2 回复

amh 安装workerman PHP-CLI 命令行模式下 扩展支持pcntl

walkor 回复了问题 • 3 人关注 • 2018-10-15 15:42 • 来自相关话题

2 回复

封装composer包,实现thinkphp、larverl、yii框架中使用(使用框架实现回调方法)

879042886 发表了文章 • 2 个评论 • 1789 次浏览 • 2018-08-08 17:23 • 来自相关话题

内有demo https://blog.majiameng.com/workerman_socket.html 可以查看我写的即时通讯案例 https://www.majiameng.com 完美解决在 event 里面 ,onmess ...查看全部

内有demo https://blog.majiameng.com/workerman_socket.html


可以查看我写的即时通讯案例 https://www.majiameng.com


完美解决在 event 里面 ,onmessage 里面如何 嵌入 thinkphp,使用框架实现回调方法


1.安装[code]composer require tinymeng/worker-socket dev-master[/code]
2.业务逻辑
在项目 /application/index/controller 下创建文件Events.php
首先创建控制器类并继承 tinymeng\worker\Server,然后设置属性和添加回调方法


2.1 ThinkPhp框架示例如下:


<?php
/**
* Name: Events.php.
* Author: JiaMeng <666@majiameng.com>
* Description: websocket callback
*/
namespace app\index\controller;

use tinymeng\worker\Server;
use GatewayWorker\Lib\Gateway;

class Events extends Server{
/**
* @var string Socket connect address
*/
protected $socket = 'websocket://0.0.0.0:1314';
/**
* @var string The current class of namespace
*/
protected $eventHandler = 'app\index\controller\Events';

/**
* Description: 当客户端连接时时触发
* Author: JiaMeng <666@majiameng.com>
* @param $client_id
*/
public static function onConnect($client_id){
echo 'client_id : '.$client_id. ', connect ' .PHP_EOL;
}

/**
* Description: 当客户端发来消息时触发
* Author: JiaMeng <666@majiameng.com>
* @param int $client_id 连接id
* @param string $data 具体消息
*/
public static function onMessage($client_id, $data) {
echo 'client : '.$client_id. ',message data :'.$data .PHP_EOL;
}

/**
* Description: 当客户端断开连接时触发
* Author: JiaMeng <666@majiameng.com>
* @param int $client_id 连接id
*/
public static function onClose($client_id) {
echo 'client_id : '.$client_id .' close '.PHP_EOL;
}

}

3.配置thinkphp的启动文件然后来启动 workman 这样回调就会主动回调回写的控制中了,可以试一试哦
4.支持workerman所有的回调方法定义(回调方法必须是public static类型)

1 回复

php7安装event的两个问题

lrjxgl 发表了文章 • 1 个评论 • 1452 次浏览 • 2018-07-26 17:16 • 来自相关话题

按照官方的event安装教程,发现两个问题。 1.按照 echo extension=event.so > /etc/php.d/30-event.ini 这个引入模块会出错 改成 echo extension=event.so ...查看全部

按照官方的event安装教程,发现两个问题。
1.按照 echo extension=event.so > /etc/php.d/30-event.ini 这个引入模块会出错
改成 echo extension=event.so > /etc/php.d/zevent.ini


2.安装的时候 有项namespace 要改为no ,不然wokerman会提示找不到eventbase;

0 回复

WebSocket客户端连接不上和掉线的问题以及解决方案

jonychen1 发表了文章 • 0 个评论 • 2211 次浏览 • 2018-07-16 18:26 • 来自相关话题

看到最近有很多人在blog上问关于WebSocket客户端连接和掉线的问题,有一部分我以前也遇到过,在这里我罗列了一些我以前遇到过的问题和其他简单的WebSocket服务。 Q1 客户端连接不上的问题 这个问题我遇到的基本集中在后端,遇 ...查看全部

看到最近有很多人在blog上问关于WebSocket客户端连接和掉线的问题,有一部分我以前也遇到过,在这里我罗列了一些我以前遇到过的问题和其他简单的WebSocket服务。


Q1 客户端连接不上的问题
这个问题我遇到的基本集中在后端,遇到的话需要收集日志进行分析,首先要查看是因为什么原因出现问题的,不过这种情况下多半会出现Exception,从Exception着手分析问题即可。


Q2 1客户端掉线问题
这里所指的客户端掉线的问题是指客户端非正常掉线。有的时候会出现Exception,通过处理Exception即可关闭连接,有的时候像客户端突然网络离线的情况下,这个时候后台没有收到客户端发来的close frame,会认为这个客户端还是处在在线的情况。那这个问题可以通过心跳包的问题解决。也可以通过客户端向服务器发心跳包,也可以服务器发送心跳包至客户端。我们最终采用的方案是客户端向服务端发送心跳包,服务端收到心跳包后会检测当前websocket session队列中是否存在同属性的websocket session,有的话关闭以前的session即可。我个人认为还是服务端像客户端发送心跳包好一些,但这样做要注意性能问题。


Q3 websocket连接后1分钟自动断开关闭
这是因为websocket长连接有默认的超时时间(1分钟,由proxy_read_timeout决定),就是超过一定的时间没有发送任何消息,连接会自动断开。解决办法就是让浏览器每隔一定时间(要小于超时时间)发送一个心跳。
代码如下:


window.setInterval(function(){ //每隔5秒钟发送一次心跳,避免websocket连接因超时而自动断开
var ping = {"type":"ping"};
ws.send(JSON.stringify(ping));
},5000);

Q4 能收到客户端消息,后台也显示发送,但是客户端收不到
这个问题经我个人观察和猜测,应该是websocketsession没有成功关闭或者成功关闭但是和一个websocketsession对应多个session有关,因为我把上面的问题解决了之后,这个问题也没出现过了。
希望我遇到的问题以及解决方案对你们有所帮助。

0 回复

客户端连接WebSocket服务器时连接失败会出现的一些问题及解决方案

jonychen1 发表了文章 • 0 个评论 • 1103 次浏览 • 2018-07-16 18:26 • 来自相关话题

前段时间自己在本地写的一个WebSocket程序部署到服务器出现了几个错误,我自己也做了记录和分析,最后总结出来给大家看一下,希望能帮助大家规避掉这些问题。同时给现在正在做WebSocket和正准备想做WebSocket的程序猿多指出一条较为效率的路。 ...查看全部

前段时间自己在本地写的一个WebSocket程序部署到服务器出现了几个错误,我自己也做了记录和分析,最后总结出来给大家看一下,希望能帮助大家规避掉这些问题。同时给现在正在做WebSocket和正准备想做WebSocket的程序猿多指出一条较为效率的路。


Q1 客户端链接WebSocket服务器时连接失败,报404错误。
排查了地址错误和配置相关后,是因为JAR冲突,因为Tomcat7及以上,在Tomcat根目录的lib里已经有了websocket-api.jar,部署项目的时候如果项目的wen-inf的lib下也有websocket-api-jar会冲突,导致连接不上。


Q2 本地运行正常,连接到服务器上时出错
本地环境:JDK1.8(32位)Tomcat8.0
服务器环境:JDK1.8(64位)Tomcat8.0
本地运行是正常的,在服务器部署运行时可以打开页面,并执行WebSocket连接失败并关闭的代码。
在Tomcat8部署的项目不要导入catalina.jar和WebSocket-api.jar这两个包,因为Tomcat8自带有这两个包。自己再导入的话会有冲突,还有就是IP写服务器的IP地址,不要写localhost,在调试远程服务器的Websocket程序时,要关闭本地的Tomcat服务器。


Q3 客户端链接WebSocket服务器成功后,测试消息推送获取session失败。(java WebSocket)
这时一定要注意,客户端连接服务器时的地址如果使用了localhost:8080/,那么浏览器也一定要使用localhost:8080/,不然两个请求不是同一个session,所以会获取不到。


Q4 WebSocket链接服务器报302错误
链接重定向,可以检查一下是不是后台访问机制的问题,还有可以看一下是不是URL路径不对。


这是我写的WebSocket程序部署到服务器出现的几个错误以及解决方法,如果我下次还会用到WebSocket这块我可能会选择第三方的……不想在这块花太多时间。
希望这次的解答对你们有所帮助。

0 回复

WebSocket配置中会遇到的一些问题

jonychen1 发表了文章 • 0 个评论 • 1555 次浏览 • 2018-07-16 18:25 • 来自相关话题

今天来整理一些我在工作中配置上遇到的一些问题。 Q1使用nginx代理后不能访问。报错WARNING:tornado.access:400 GET /ws (127.0.0.1) 0.79ms 或者连接失败后会反复发起连接请求。 需要在 ...查看全部

今天来整理一些我在工作中配置上遇到的一些问题。


Q1使用nginx代理后不能访问。报错WARNING:tornado.access:400 GET /ws (127.0.0.1) 0.79ms 或者连接失败后会反复发起连接请求。
需要在nginx的location中新增如下配置[code]# websocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";[/code]
Q2 跨域问题(spring websocket)
经查阅官方文档springwebsocket 4.1.5版本前默认支持跨域访问,之后的版本默认不支持跨域,需要设置[code].setAllowedOrigins(“”)
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myWebSocketHandler(), "/echo.do").setAllowedOrigins("
").addInterceptors(new HandshakeInterceptor());


registry.addHandler(myWebSocketHandler(), "/echo").setAllowedOrigins("").addInterceptors(new HandshakeInterceptor())
.withSockJS();
}[/code]
Q3
.do配置导致sockjs失效问题(spring websocket)
web.xml中添加如下配置[code]<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/sockjs/</url-pattern>
</servlet-mapping>[/code]websocket配置类中路径中不加sockjs[code]registry.addHandler(myWebSocketHandler(), "/echo").setAllowedOrigins("
").addInterceptors(new HandshakeInterceptor())
.withSockJS();[/code]前端调用时应加上sockjs[code]websocket = new SockJS("http://127.0.0.1:8080/WebSocketTest001/sockjs/echo");[/code]
Q4 一个websocketsession对应多个session的情况
这是由于尽管每次都能保证有一个websocketsession存在,但客户端没连接一次tomcat都建立一个链接,除非你在请求头里添加cookie:JSESSIONID=xxxxxx


Q5 能收到客户端消息,后台也显示发送,但是客户端收不到
这个问题经我个人观察和猜测,应该是websocketsession没有成功关闭或者成功关闭但是和一个websocketsession对应多个session有关,因为我把上面的问题解决了之后,这个问题也没出现过了。

1 回复

GatewayClient 无法使用的问题

alex0048 发表了文章 • 1 个评论 • 1280 次浏览 • 2018-07-09 17:32 • 来自相关话题

有时候会出现 调用GatewayClient中的sendToClient 方法不能给客户端发送指令的问题,请注意查看 数据库存储的client_id 是否为当前进程的client_id。 注意表结构和表字段的类型,如果有错误会造成写入的client ...查看全部

有时候会出现 调用GatewayClient中的sendToClient 方法不能给客户端发送指令的问题,请注意查看 数据库存储的client_id 是否为当前进程的client_id。


注意表结构和表字段的类型,如果有错误会造成写入的client_id与实际进程不相符 导致无法发送指令。


图片

0 回复

workerman实战教程,利用websocket完成即时通讯,在线客服

hehe 发表了文章 • 0 个评论 • 2838 次浏览 • 2018-06-28 18:16 • 来自相关话题

工作需要使用了workerman的getwayworker实现了长连接的点对点在线客服系统,真心感觉到workerman的强大,以及getwayworker对来实现长连接是多吗容易上手,在tp5上实现的,过程踩了不少坑,特意出了一个视频教程,有需要的可以看一下 ...查看全部

工作需要使用了workerman的getwayworker实现了长连接的点对点在线客服系统,真心感觉到workerman的强大,以及getwayworker对来实现长连接是多吗容易上手,在tp5上实现的,过程踩了不少坑,特意出了一个视频教程,有需要的可以看一下。


[url=http://study.163.com/course/introduction/1005015012.htm?share=2&shareId=400000000388007]workerman实战之php在线客服[/url]


[img]http://edu-image.nosdn.127.net/3c2c52c6-99cf-49fe-a984-e95c5cb54dd7.png[/img]


[img]http://edu-image.nosdn.127.net/6eee3de7-12b2-438c-b5b5-d7f2aed09d35.png[/img]


[img]http://edu-image.nosdn.127.net/ad9ded2a-b167-4492-b483-33b1522f454f.png[/img]

3 回复

HTTP协议下的IO阻塞任务,如何保证请求均匀分配到进程

whyme 发表了文章 • 3 个评论 • 1773 次浏览 • 2018-05-31 16:38 • 来自相关话题

比如以HTTP协议创建了5个进程,然后每个进程里的业务执行时间是2秒。 然后并发4个用户,16次请求 ab -c 4 -n 16 'http://127.0.0.1:8080/' 结果请求并没有均匀分配到进程,某个进程对某个 ...查看全部

比如以HTTP协议创建了5个进程,然后每个进程里的业务执行时间是2秒。


然后并发4个用户,16次请求


ab -c 4 -n 16 'http://127.0.0.1:8080/'

结果请求并没有均匀分配到进程,某个进程对某个请求进行了阻塞


2018-05-31 15:48:22.6796 service_log 'executeTime:2.0680978298187'
2018-05-31 15:48:22.6797 service_log 'executeTime:2.0688819885254'
2018-05-31 15:48:22.6799 service_log 'executeTime:2.069078207016'
2018-05-31 15:48:22.6801 service_log 'executeTime:2.0692131519318'

2018-05-31 15:48:24.6901 service_log 'executeTime:2.0080099105835'
2018-05-31 15:48:24.6901 service_log 'executeTime:2.0079879760742'

2018-05-31 15:48:26.6998 service_log 'executeTime:2.0084669589996'
2018-05-31 15:48:26.6998 service_log 'executeTime:2.0076689720154'
2018-05-31 15:48:26.6998 service_log 'executeTime:2.0074841976166'
2018-05-31 15:48:26.6999 service_log 'executeTime:2.0084929466248'

2018-05-31 15:48:28.7072 service_log 'executeTime:2.0056090354919'
2018-05-31 15:48:28.7073 service_log 'executeTime:2.0057229995728'
2018-05-31 15:48:28.7073 service_log 'executeTime:2.0057458877563'

2018-05-31 15:48:30.7168 service_log 'executeTime:2.0076150894165'
2018-05-31 15:48:30.7171 service_log 'executeTime:2.0088679790497'
2018-05-31 15:48:30.7173 service_log 'executeTime:2.0084898471832

可以通过在建立连接后暂停请求,结束后关闭请求的方式,避免多个请求阻塞到一个进程中执行:


$worker->onConnect = function(TcpConnection $connection)use($worker) {
$worker->pauseAccept(); // 暂停接收请求

// 接收消息回调
$connection->onMessage = function (TcpConnection $connection, $data) {
// 耗时代码
$connection->close('success‘); // 关闭连接
};
};
$worker->onClose = function(TcpConnection $connection)use($worker) {
$worker->resumeAccept(); // 恢复接收请求
};

最后就得到了如下结果:


2018-05-31 16:34:22.2053 service_log 'executeTime:2.058121919632'
2018-05-31 16:34:22.2053 service_log 'executeTime:2.0567870140076'
2018-05-31 16:34:22.3065 service_log 'executeTime:2.1597728729248'
2018-05-31 16:34:22.3065 service_log 'executeTime:2.1576390266418'

2018-05-31 16:34:24.3266 service_log 'executeTime:2.1183500289917'
2018-05-31 16:34:24.3267 service_log 'executeTime:2.1188020706177'
2018-05-31 16:34:24.4269 service_log 'executeTime:2.1188299655914'
2018-05-31 16:34:24.4272 service_log 'executeTime:2.118989944458'

2018-05-31 16:34:26.4402 service_log 'executeTime:2.1116380691528'
2018-05-31 16:34:26.4402 service_log 'executeTime:2.1118400096893'
2018-05-31 16:34:26.5402 service_log 'executeTime:2.1115860939026'
2018-05-31 16:34:26.5402 service_log 'executeTime:2.1114778518677'

2018-05-31 16:34:28.4544 service_log 'executeTime:2.0095458030701'
2018-05-31 16:34:28.5551 service_log 'executeTime:2.1137058734894'
2018-05-31 16:34:28.6556 service_log 'executeTime:2.1138899326324'
2018-05-31 16:34:28.6556 service_log 'executeTime:2.1138758659363'

以上是单一的HTTP协议的worker模型,之后我试过用HTTP协议的worker接收请求,然后异步转发给TCP协议的worker进行处理,发现如果一次性发起多个请求时,TCP协议的worker也会出现类似上面的情况,然后通过上面的方法,也很完美的解决了这个问题。


最后我觉得pauseAccept()、resumeAccept()应用场景很多啊,比如生存消费者模型中,消费者IO阻塞等待任务,又比如对时间点要求较高的爬虫任务,能尽量的均匀分配请求到进程,进而减少时间差。


官方文档目前还没有相应描述,我还是好不容易找了一篇文章中的关键字pauseAccept,然后搜索源码发现的。。。这真是两个神奇的方法,希望官方能在后面补充一下,对于我这类的小白就帮助大了,哈哈哈

1 回复

小程序与GatewayWorker建立连接及 apache 配置 https 转发

默然 发表了文章 • 1 个评论 • 2747 次浏览 • 2018-03-08 09:30 • 来自相关话题

1、apache 加载 proxy_module ,proxy_wstunnel_module 模块 2、apache 配置 ssl [code]# Proxy Config SSLProxyEngine on 监听的路径和 ...查看全部

1、apache 加载 proxy_module ,proxy_wstunnel_module 模块
2、apache 配置 ssl [code]# Proxy Config
SSLProxyEngine on


监听的路径和转发的路径,需要输入https://和最后面的/


ProxyRequests Off
ProxyPass /wss ws://0.0.0.0:8585
ProxyPassReverse /wss ws://0.0.0.0:8585[/code]
3、小程序代码[code]var that = this;
wx.connectSocket({
url: "wss://www.xxx.com/wss"
});[/code]
4、GatewayWorker 代码[code]$gateway = new Gateway('websocket://0.0.0.0:8585', $context);[/code]
原文链接:[url=[url=https://sevming.github.io/Php/wxapp-websocket.html#more]微信小程序 - websockt wss[/url]]微信小程序 - websocket wss[/url]

15 回复

写了一个基于 workerman 的小型 HTTP 框架 WorkerA

wazsmwazsm 发表了文章 • 15 个评论 • 3538 次浏览 • 2017-09-27 10:17 • 来自相关话题

2018-07-29 更新 最小版本升级 1.1.0 ==> 1.1.1 完善 IOC 容器,解决 IOC 容器只能依赖注入一层的问题。 修复平滑启动不能响应配置修改的问题。 修复动态路由缓存内存泄漏导致进 ...查看全部

2018-07-29 更新


最小版本升级 1.1.0 ==> 1.1.1


完善 IOC 容器,解决 IOC 容器只能依赖注入一层的问题。


修复平滑启动不能响应配置修改的问题。


修复动态路由缓存内存泄漏导致进程重启的问题,使用 LRU 算法控制了缓存的大小。


2018-07-15 更新


WorkerA 发布了 1.1 版本,新增了中间件、动态路由、异常 Handler 等功能。


修复若干 bug,改善了很多不足。


编写了框架的文档:[url=https://www.kancloud.cn/wazsmwazsm/workera/691859]文档地址[/url]。


2017-10-18 更新
添加了 redis 支持


=============


接触 workerman 一个多月,让我重新认识了 PHP。很佩服 workerman 的性能和稳定性。


之前有用 laravel \ lumen 做一个接口的项目,虽然 laravel 的开发很方便,但是由于服务器配置和传统 wnmp wamp 模型的限制,QPS 小的可怜。于是用 workerman 写一个小型的 HTTP 框架的想法就出来了。


WorkerA 目前只写了一个月不到,第一版发布,实现了一些基础功能。
使用自动依赖注入
提供 mysql 驱动、支持断线重连,跑在进程单例模式中
提供单例模式,可以注册需要的类为单例
支持 gizp deflate 压缩传输


个人喜欢 laravel 的查询构造器和路由的风格,于是将路由和查询构造器都写成了 laravel 风,你可以像 laravel 一样的方式写路由和查数据库。


WorkerA 没有 view 层,更适合做接口的项目,当然目前框架没有提供 JWT 等验证功能,要完成一个标准的 webapi ,你需要自己写一些东西。不过有自动依赖注入的功能,扩展也不算难题。


在此谢谢 workerman 的作者,虽然只是用了 workerman 功能的一小部分,但是依然解决了我自己的很多业务上的难题。


Github 地址 :[url]https://github.com/wazsmwazsm/WorkerA[/url]
框架核心部分地址 :[url]https://github.com/wazsmwazsm/WorkerF[/url]

2 回复

实现http与workerman的交互demo

woeoio 发表了文章 • 2 个评论 • 5105 次浏览 • 2017-08-15 12:55 • 来自相关话题

这里主要用到wm的聊天室demo做了一个示例。 <?php /** * This file is part of workerman. * * Licensed under The MIT Li ...查看全部

这里主要用到wm的聊天室demo做了一个示例。


<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
use \Workerman\Worker;
use \Workerman\WebServer;
use \GatewayWorker\Gateway;
use \GatewayWorker\BusinessWorker;
use \Workerman\Autoloader;
use \Workerman\Connection\AsyncTcpConnection;
// 自动加载类
require_once __DIR__ . '/../../Workerman/Autoloader.php';
Autoloader::setRootPath(__DIR__);

// 这个55150端口不解析web内容,主要用于post和get,
//在web——onMessage事件取出$_GET和$_POST的数据,相当于仅作API接口
$web = new Worker("http://0.0.0.0:55150");
// WebServer进程数量固定为1
$web->count = 1;

//开启一个AsyncTcpConnection转发器
// 设置访问对方主机的本地ip及端口(每个socket连接都会占用一个本地端口)
$context_option = array(
'socket' => array(
// ip必须是本机网卡ip,并且能访问对方主机,否则无效
'bindto' => '127.0.0.1:2333',
),
);

$con = new AsyncTcpConnection('ws://127.0.0.1:7272', $context_option);
//用ATC做中转来和Http的数据做收发交互
//主要WEB启动的时候做ATC转发器的事件初始化
$web->onWorkerStart = function($web)
{

file_get_contents('http://a-vi.com/api/send2qw/?WorkerMan_Web回调:服务启动');
// $web->send('receive success');
// 这个网址是我的消息接收器,

GLOBAL $con;
// 中转器链接到workerman的时候自动以一个客户端身份登录,并保持在线
$con->onConnect = function($con) {
$con->send('{"type":"login","client_name":"邓伟(企业微信小秘书)","room_id":"1"}');
};
// 中转器收到workerman消息的时候做转发处理
$con->onMessage = function($con, $dat) {
echo "\r\n GC收到来自GM的消息:$dat \r\n \r\n";
$data=json_decode($dat);
switch($data->type){
// 服务端ping客户端
case 'ping':
$con->send('{"type":"pong"}');
break;;
// 登录 更新用户列表
case 'login':
//{"type":"login","client_id":xxx,"client_name":"xxx","client_list":"[...]","time":"xxx"}
file_get_contents('http://a-vi.com/api/send2qw/?电子黑板回发【登录】'.$data->client_name);
break;
// 客户端发言 message: {type:say, to_client_id:xx, content:xx}
case 'say':
//{"type":"say","act":"talk_text","from_client_id":xxx,"to_client_id":"all/client_id","content":"xxx","time":"xxx"}
if($data->act=='talk_text'){
$newmsg=$data->content;
}else{
$newmsg='<其他消息>:'.substr($dat,100);
}
file_get_contents('http://a-vi.com/api/send2qw/?电子黑板回发【消息】('.$data->from_client_name.'说:)'.$newmsg);
break;
// 登录 更新用户列表
case 'logout':
//{"type":"logout","from_client_id":xxx,"from_client_name":"xxx","time":"xxx"}
file_get_contents('http://a-vi.com/api/send2qw/?电子黑板回发【关闭】'.$data->from_client_name);
break;
}
};
// 开始登录
$con->connect();
};
// web worker 收到来自http数据的时候取出来通过ATC转发给workerman
$web->onMessage = function($conn, $data)
{
if(isset($data['get']['msg'])){ //注意 ico 请求过滤
GLOBAL $con;
$con->send('{"type":"say","act":"talk_text","to_client_id":"all","to_client_name":"所有人","content":"'.$data['get']['msg'].'"}');
//ws.send('{"type":"say","act":"talk_text","to_client_id":"all","to_client_name":"所有人","content":"【图灵回答】:'+data.text+'"}');
// file_get_contents('http://a-vi.com/api/send2qw/?WorkerMan_Web回执:'.$data['get']['msg']);
// $web->send('receive success');
print_r($data['get']['msg']);
}
$conn->close("hello\n");
};

// 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}

其他就看附件了吧。


--------------------------------补充,源本发了完整demo附件,被人说有广告嫌疑,已经删除。不再分享

0 回复

workerman源码分析

church 发表了文章 • 0 个评论 • 2139 次浏览 • 2017-03-27 09:56 • 来自相关话题

workerman workerman源码分析 [list] []前置知识[/] [][url=https://github.com/fireqong/workerman/blob/master/Event.md ...查看全部
7 回复

pcntl_alarm定时闹钟信号详解

haotian 发表了文章 • 7 个评论 • 5579 次浏览 • 2017-02-28 11:23 • 来自相关话题

目的:主要测试pcntl_alarm的功能 测试代码一 <?php declare(ticks = 1); function signal_handler($signal) { ...查看全部

目的:主要测试pcntl_alarm的功能
测试代码一


<?php
declare(ticks = 1);

function signal_handler($signal) {
print "catch you ";
pcntl_alarm(5);
}

pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);

while(1) {
}
?>

正确代码,证实每次pcntl_alarm预定一个计时器,然后当计时器到时间的时候,就会给当前进程触发SIGALRM信号


测试代码二


<?php
declare(ticks = 1);

function signal_handler($signal) {
print "catch you ";
// pcntl_alarm(5);
}

pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);

while(1) {
}
?>

注释掉触发函数中的信号计时器,我们发现信号只触发了一次,这就说明pcntl_alarm只会一次触发


测试代码三


<?php
declare(ticks = 1);

function signal_handler($signal) {
print "catch you ";
// pcntl_alarm(5);
}

pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);

sleep(100);

将while循环换成sleep,发现在5秒的时候信号触发,程序结束,这是为什么呢,对应手册中提到的注意点,pcntl_alarm信号会提前结束掉sleep()函数


测试代码四
之前发过文章说过declare和pcntl_signal_dispatch两个函数的区别,那么这里用后面一个函数实现呢?


<?php
function signal_handler($signal) {
print "Caught SIGALRM ";
pcntl_alarm(5);
}

pcntl_signal(SIGALRM, "signal_handler", true);
pcntl_alarm(5);

for(;;) {
sleep(100);
pcntl_signal_dispatch();
}
?>

这里我用到了一个特性 第一个特性就是sleep和SIGALRM信号之间的关系
其实你看可以去掉sleep,但是这样dispatch函数调用就太频繁,会对系统造成压力

3 回复

关于declare和posix_signal_dispatch()的验证和说明

haotian 发表了文章 • 3 个评论 • 1507 次浏览 • 2017-02-26 16:17 • 来自相关话题

下面都是自己程序验证的,如果有说的不对的, 请指出 验证原因:之前自己看workerman源码,写相关的信号,进程程序,模仿网上开始写,碰到declare(ticks=1),查资料说是让进程自己检测信号,数字是隔多少时间检测多少,今天碰巧看到wor ...查看全部

下面都是自己程序验证的,如果有说的不对的, 请指出
验证原因:之前自己看workerman源码,写相关的信号,进程程序,模仿网上开始写,碰到declare(ticks=1),查资料说是让进程自己检测信号,数字是隔多少时间检测多少,今天碰巧看到workerman信号分发那段代码,发现使用了一个函数posix_signal_dispatch(),资料说该函数是:调用等待信号的处理器,也摸不着头绪这个函数到底是干嘛的
测试代码一:


echo "安装信号处理器...\n";
echo "父进程: ".posix_getpid()."\n";
pcntl_signal(SIGHUP, function($signo) {
echo posix_getpid()." 信号处理器被调用\n";
});

$pid = pcntl_fork();
if($pid){
//parent
echo "这里是父进程".posix_getpid()."\n";
echo "刚刚新建了子进程".$pid."\n";
posix_kill($pid, SIGHUP);
}else{
echo "我是子进程".posix_getpid()."\n";
}
//pcntl_signal_dispatch();
echo "完成\n";

这段代码里面我没有使用declare和pcntl_signal_dispatch,可以发现处理信号函数并没有被触发


测试代码二


declare(ticks=1);
echo "安装信号处理器...\n";
echo "父进程: ".posix_getpid()."\n";
pcntl_signal(SIGHUP, function($signo) {
echo posix_getpid()." 信号处理器被调用\n";
});

$pid = pcntl_fork();
if($pid){
//parent
echo "这里是父进程".posix_getpid()."\n";
echo "刚刚新建了子进程".$pid."\n";
posix_kill($pid, SIGHUP);
}else{
echo "我是子进程".posix_getpid()."\n";
}
echo "完成\n";

发现信号检测函数被触发了,这就说明declare(ticks=1);确实可以帮助进程检测接收到的信号
但是这个函数ticks后面的数值代表着基础代码执行了多少次后会被检测一次,基础代码是什么,如何检测,喜欢这块的可以自己查查
那么如果我们将ticks后面的数值调到1000,你就会发现信号又接收不到了,为什么,留给自己思考


测试代码三


echo "安装信号处理器...\n";
echo "父进程: ".posix_getpid()."\n";
pcntl_signal(SIGHUP, function($signo) {
echo posix_getpid()." 信号处理器被调用\n";
});

$pid = pcntl_fork();
if($pid){
//parent
echo "这里是父进程".posix_getpid()."\n";
echo "刚刚新建了子进程".$pid."\n";
posix_kill($pid, SIGHUP);
}else{
echo "我是子进程".posix_getpid()."\n";
}
pcntl_signal_dispatch();
echo "完成\n";

这里发现信号触发函数也被触发了,因为这个函数的作用就是检测当前进程有没有接收到信号,如果有,就按照信号处理函数之前已经绑定的处理方法进行处理


测试代码


echo "安装信号处理器...\n";
echo "父进程: ".posix_getpid()."\n";
pcntl_signal(SIGHUP, function($signo) {
echo posix_getpid()." 信号处理器被调用\n";
});

$pid = pcntl_fork();
if($pid){
//parent
echo "这里是父进程".posix_getpid()."\n";
echo "刚刚新建了子进程".$pid."\n";
sleep(1);
posix_kill($pid, SIGHUP);
}else{
echo "我是子进程".posix_getpid()."\n";
}
pcntl_signal_dispatch();
sleep(2);
echo "完成\n";

这个稍微有个小改动就是让主进程睡了一秒,(听了@walkor的意见)但是在接受消息之后子进程和父进程都睡两秒,这样就排除了子进程已经结束,父进程发送信号没有接受者
然后再发送信息,这样做主进程就在子进程调用dispatch函数之后才发送了信息,我做这个主要就是为了验证,dispatch是一个让进程一直持有的状态,还是只执行一个动作,结果就是子进程没有接收到信号