如何在php后端及时推送消息给客户端

0

walkor大神,目前需求是这样的:


有一群商家在后台网页处理批量导入产品 -》 服务器接受请求 -》 开始foreach一个一个处理导入请求;


我现在想每成功导入一个就推送到前台显示已经导入成功,直到全部导入自动结束推送。


看了聊天室代码,消息推送都是靠前端js+event.php,我想直接在php里面不需要onMessage触发.


我从下午看到现在文档,也看了很多问答,依然非常糊涂,不奢望给整段代码,但是希望walkor大神给点思路。

已邀请:

后端代码
push.php


<?php
use Workerman\Worker;
require_once './Workerman/Autoloader.php';
// 初始化一个worker容器,监听1234端口
global $worker;
$worker = new Worker('websocket://0.0.0.0:1234');
// 这里进程数必须设置为1
$worker->count = 1;
// worker进程启动后建立一个内部通讯端口
$worker->onWorkerStart = function($worker)
{
// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$inner_text_worker = new Worker('Text://0.0.0.0:5678');
$inner_text_worker->onMessage = function($connection, $buffer)
{
global $worker;
// $data数组格式,里面有uid,表示向那个uid的页面推送数据
$data = json_decode($buffer, true);
$uid = $data['uid'];
// 通过workerman,向uid的页面推送数据
$ret = sendMessageByUid($uid, $data['percent']);
// 返回推送结果
$connection->send($ret ? 'ok' : 'fail');
};
$inner_text_worker->listen();
};
// 新增加一个属性,用来保存uid到connection的映射
$worker->uidConnections = array();
// 当有客户端发来消息时执行的回调函数
$worker->onMessage = function($connection, $data)use($worker)
{
// 判断当前客户端是否已经验证,既是否设置了uid
if(!isset($connection->uid))
{
// 没验证的话把第一个包当做uid(这里为了方便演示,没做真正的验证)
$connection->uid = $data;
/* 保存uid到connection的映射,这样可以方便的通过uid查找connection,
* 实现针对特定uid推送数据
*/
$worker->uidConnections[$connection->uid] = $connection;
return;
}
};
// 当有客户端连接断开时
$worker->onClose = function($connection)use($worker)
{
global $worker;
if(isset($connection->uid))
{
// 连接断开时删除映射
unset($worker->uidConnections[$connection->uid]);
}
};
// 向所有验证的用户推送数据
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}
// 针对uid推送数据
function sendMessageByUid($uid, $message)
{
global $worker;
if(isset($worker->uidConnections[$uid]))
{
$connection = $worker->uidConnections[$uid];
$connection->send($message);
return true;
}
return false;
}
// 运行所有的worker(其实当前只定义了一个)
Worker::runAll();

启动后端服务
php push.php start -d


前端接收推送的js代码


var ws = new WebSocket('ws://127.0.0.1:1234');
ws.onopen = function(){
var uid = 'uid1';
ws.send(uid);
};
ws.onmessage = function(e){
alert(e.data);
};

后端推送消息的代码


// 建立socket连接到内部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的数据,包含uid字段,表示是给这个uid推送
$data = array('uid'=>'uid1', 'percent'=>'88%');
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data)."\n");
// 读取推送结果
echo fread($client, 8192);

这里的uid不一定是用户的id,也可以理解为任务id即 taskid


以上代码亲测可以直接使用

夏虫 - 电子学生

赞同来自: blogdaren none

要实现我这种模式 问号部分该用什么方法实现呢

walkor

赞同来自:


哈啰 !push.php line 4: $worker->count = 1;为什么只能设置一个进程啊



假如:
客户端1连接进程A
客户端2连接进程B


客户端2无法直接通过进程B给客户端1发送数据,因为客户端1属于进程A不属于进程B,B进程控制不到客户端1(要想两个进程之间通讯需要一些进程间通讯手段,可以使用http://doc3.workerman.net/component/channel.html)。
所以所有客户端都只能连接同一个进程才能直接互相通讯,为了避免客户端连到不同进程,count设置为1。

ivan - 极客男

赞同来自:

写得太好了!

workercat - 80hou IT boy

赞同来自:

我想问,workman 怎么与 PHP 脚本进行数据传输呢?

walkor

赞同来自:

@workercat 新问题请新建帖子。把问题描述清楚,不要问“一句话”问题,尤其不要问“XXXX怎么做“这种,这种太笼统,没法回。不同场景有不同的做法。

workercat - 80hou IT boy

赞同来自:

执行后端推送代码时,出现 unable to connect, connect refused. 请问这是什么原因造成的尼?我已经更换了多个端口进行测试,依然是同样的提示。还有什么测试方法和手段来找出原因嘛?


后端推送消息的代码
// 建立socket连接到内部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT);
// 推送的数据,包含uid字段,表示是给这个uid推送
$data = array('uid'=>'uid1', 'percent'=>'88%');
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data)."\n");
// 读取推送结果
echo fread($client, 8192);

workercat - 80hou IT boy

赞同来自:

workerman 代码是完全与上面的一致。

walkor

赞同来自:

发生这个问题原因有
1.服务端没启动
2.端口错误
3.客户端IP错了
4.防火墙挡住了

workercat - 80hou IT boy

赞同来自:

首先,我先建一个 ump worker , 再在 work 进程启动后建立一个内部通讯端口。然后,我再写 php socket 脚本来反问内部通讯端口,提示 connect refused。防火墙是否阻挡,端口是否被占用,服务是否开启,客户端链接的 ip 都这几个问题都确认没有问题。


结果,却是一直提示 connection refused.


{{{
use Workerman\Worker;
include './Workerman/Autoloader.php';


$worker = new Worker("udp://192.168.50.190:8800");
//var_dump($worker);
$worker->count = 1;

$worker->onWorkerStart = function ($worker)
{
// 用于 Laravel 与 Workerman 的内部通讯
$inner_text_worker = new Worker ("text://192.168.50.190:5678");

// 接收到 Laravel 请求信息,就向设备发起 UDP 请求
$inner_text_worker->onMessage = function ($connection, $arr_data)
{

$connection->send('success udp');
};
};

// 接收 UDP 请求,如:心跳
$worker->onMessage = function ($connection, $data)
{
echo $data . "<br/>";
$client_ip = $connection->getRemoteIp();
$client_port = $connection->getRemotePort();
$connection->send(strrev($data));
};

}}


// 开启内部通讯端口打印输出的对象:object(Workerman\Worker)#6 (24) {
=>
int(0)
=>
string(4) "none"
=>
int(1)
=>
string(0) ""
=>
string(0) ""
=>
bool(true)
=>
bool(false)
=>
NULL
=>
NULL
=>
object(Closure)#7 (1) {
=>
RECURSION
}
=>
NULL
=>
NULL
=>
NULL
=>
NULL
=>
NULL
=>
NULL
=>
string(3) "tcp"
=>
array(0) {
}
=>
string(0) ""
=>
string(37) "/Library/WebServer/Documents/camerawk"
=>
NULL
=>
string(26) "text://192.168.50.190:5678"
=>
resource(17) of type (stream-context)
=>
string(32) "000000000f4c6bca0000000043696ce7"
}


// php socket 脚本对内部端口通讯发起请求,报错:stream_socket_client(): unable to connect to tcp://192.168.50.190:5678 (Connection refused)

walkor

赞同来自:

少了一句$inner_text_worker->listen();
归根到底还是对应的端口服务没启动。

workercat - 80hou IT boy

赞同来自:

问题:如果我 new 的 worker 是监听是的 http 通讯 8080端口,当有接收到信息时,那么 workerman 的onMessage 方法执行 $connection->send("message") 。


我的疑问时 $connection->send("message") 发送的信息,会以什么端口,从服务器发出去呢?


$worker->onMessage = function($connection, $data) use ($worker)
{
$connection->send("返回信息");
};

walkor

赞同来自:

@workercat 新问题开新的帖子吧。

夏虫 - 电子学生

赞同来自:

上面的没看得太明白,push.php是运行在workman里面的 后端推送消息代码是运行在web后台的对吗,通过后端推送消息代码调用push.php.是这个思路吗

walkor

赞同来自:

@夏虫 对

小V - 80后|IT

赞同来自:

$inner_text_worker = new Worker('Text://0.0.0.0:5678');
第一次运行没有问题,但第二次运行


fwrite($client, json_encode($data)."\n");
这里会报错,换一个端口运行,又可以了,这是为什么呢?

httpp886

赞同来自:

use Yii;
use yii\console\Controller;
use \Workerman\Worker;
require_once 'vendor/autoload.php';

class WorkerController extends Controller {
public function actionPush(){
// 初始化一个worker容器,监听1234端口
require_once dirname(Yii::$app->basePath).'/vendor/workerman/workerman/Autoloader.php';
$worker = new Worker('websocket://127.0.0.1:1234');

// 这里进程数必须设置为1
$worker->count = 1;
// worker进程启动后建立一个内部通讯端口
$worker->onWorkerStart = function($worker)
{
// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$inner_text_worker = new Worker('http://127.0.0.1:5678');
$inner_text_worker->onMessage = function($connection, $buffer)
{
global $worker;
// $data数组格式,里面有uid,表示向那个uid的页面推送数据
$data = json_decode($buffer, true);
$uid = $data;
// 通过workerman,向uid的页面推送数据
$ret = $this->sendMessageByUid($uid, $buffer);
// 返回推送结果
$connection->send($ret ? 'ok' : 'fail');
};
$inner_text_worker->listen();
};
// 新增加一个属性,用来保存uid到connection的映射
$worker->uidConnections = array();
// 当有客户端发来消息时执行的回调函数
$worker->onMessage = function($connection, $data)use($worker)
{
// 判断当前客户端是否已经验证,既是否设置了uid
if(!isset($connection->uid))
{
// 没验证的话把第一个包当做uid(这里为了方便演示,没做真正的验证)
$connection->uid = $data;
/* 保存uid到connection的映射,这样可以方便的通过uid查找connection,
* 实现针对特定uid推送数据
*/
$worker->uidConnections = $connection;
return;
}
};

// 当有客户端连接断开时
$worker->onClose = function($connection)use($worker)
{
global $worker;
if(isset($connection->uid))
{
// 连接断开时删除映射
unset($worker->uidConnections);
}
};
// 运行所有的worker(其实当前只定义了一个)
Worker::runAll();
}

// 向所有验证的用户推送数据
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}

// 针对uid推送数据
function sendMessageByUid($uid, $message)
{
global $worker;
if(isset($worker->uidConnections))
{
$connection = $worker->uidConnections;
$connection->send($message);
return true;
}
return false;
}

}

stream_socket_client(): unable to connect to tcp://127.0.0.1:5678 (������ӷ���һ��ʱ���û���ȷ�

Victoryship

赞同来自:

大佬问下,为什么我用例子发到客户端浏览器上。消息显示一下就马上消失了?

joy

赞同来自:

一天中,经常隔一两小时,sendMessageByUid发送的结果是false,过一会儿又自己好了
查看状态没有false
这种情况,需要不需要心跳,本身已是长连接

walkor

赞同来自:

如果需要链接长时间维持(大于1分钟)必须加心跳。客户端定时向服务端发送点心跳数据(数据任意,服务端能识别就行),服务端onMessage里判断是心跳忽略即可。


楼主的需求是浏览器里展示进度,预期这个进度会在1分钟内完成,则不用加心跳。

joy

赞同来自:

加了心跳,但是
用的是winform接收的,打着断点
第一次有,第二次执行结果打印出来也是成功,win端但没接收到任何消息

joy

赞同来自:

包括心跳回来的消息也一样
长连接建立后,给子线程A发,第一次正常接收,第二次接收到任务消息了

joy

赞同来自:

图片
这个断开,是否还要用心跳来判断,还是内部自动会判断
如果没发消息过来了,会不会就自己会断开

joy

赞同来自:

需要在子线程上加
图片
来监测关闭吗?
如何加?

joy

赞同来自:

感觉很不稳定,不知应该怎么来调试
客户端写了心跳,比之前的没心跳的,连接情况更差
消息返回成功的很少,而且即使返回成功,但客户端,还是没有接收到数据


以前一两小时,发生三四次失败,现在是经常失败

walkor

赞同来自:

可能你的客户端还没连上,你就开始从后端push消息了,这个demo里没做消息缓存,如果客户端不存在就直接把消息丢弃了。
自己多打打日志服务端抓抓包定位下吧,从你的描述中无法帮到你。
这个demo是没问题的,很多人在用了。

joy

赞同来自:

心跳处理这样可以吧?不需要到5678端口上吧


图片

php_zdg - 90后Phper

赞同来自:

按照demo代码来,php运行一直返回fail,是那个映射问题吗?好像不能找到制定客户端啊

mir_gong

赞同来自:

按照demo代码来,php运行一直返回fail.怎么解决~

walkor

赞同来自:

返回fail很明显对应uid没在线

xiaoxiaolu

赞同来自:

写的比较好,测试没问题,很好解决了PHP客户端与web socket端之间的数据监听通讯

dxyzz - 漫天神佛求指点。。

赞同来自:

大佬们,
能不能问一下你这个问题的第三部分:后端推送消息的代码


这一部分代码是应该写在哪里哦?跟后端代码写一个类里面么?新手求指点。。。

18117303062 - workerman爱好者

赞同来自:

写得很好,学习了。我是新手,能发表下学习体会吗?

18117303062 - workerman爱好者

赞同来自:

总体上该方案非常优秀,框架大概二个部分,第一个是websocket作为服务端接收和发送消息的父进程,不妨称之为fatherWorker,比较巧妙的是在该对象里嵌套了一个TEXT协议的worker,不妨叫他childWorker,这个功能用一个PHP页面就能解决。第二个是客户端,该客户端可以用本地建立一个简单网站实现,用来登录界面操作,里面两个页面,第一个页面是用来和服务端的父进程websocket建立连接,并发送UID的,这个页面可以是普通的html页面。第二个页面是用来和服务端的子进程TEXT协议建立连接的,其作用是发送UID编号和数据,这里要注意的是两个页面的UID号必须一致,否则发送失败。
这样,三个页面,实现了WEB服务、SOCKET服务和TEXT协议服务三者联动的效果,构思巧妙,非常优秀!

18117303062 - workerman爱好者

赞同来自:

代码我依样画葫芦测试过了,没有问题,感谢分享,我刚学习workerman,说些自己的理解,不当之处大家指正。

wegl

赞同来自:

workerman新手,根据自己入门经历的疑惑,讲一下表面的小白使用问题,也是新手小白容易产生的疑惑:
可以确定,下载workerman最新版,不修改示例代码,示例代码是百分百可以在linux和windows都可以正常运行的。
windows中出现的问题:
(1)、执行顺序!一定不能错:第一步,php push.php,第二步,浏览器的页面上运行执行js ,第三步,执行stream_socket_client的5678端口的那段代码


(2)、windows本机测试中,第二步的js,可以在任何一个浏览器的console中执行,但是要注意,如果是打算把js放进自己写的html页面,是无法用本地windows的apache服务访问页面的,原因应该就是windows下php只能用一个进程,而php进程已经被第一步占用。同样,第三步,也不能用windows本机的apache请求页面或者php命令行执行php,可以通过telnet 127.0.0.1:5678的方式,输入:{"uid":"uid1","percent":"2%"},浏览器js就可以收到了


(3)、这个代码是可以调试的(好像是废话)。可能对于小白而言,因为对workerman陌生一时忘记了怎么调试。其实和普通的php开发一样,可以直接echo/var_dump打印输出,也可以记录到文件里。
telnet 127.0.0.1 5678 的命令下,也可以调试5678接收第三步消息的信息:push.php代码里,在$connection->send($ret ? 'ok' : 'fail');前,加上自己要调试的内容send即可,如:$connection->send($buffer);
   !! push.php调试代码修改后,一定要重新启动第一步(linux下restart/reload;windows下退出进程,重新执行),才能生效
    linux下执行php push.php start 后面不加-d,不让后台运行,可以查看打印出的调试输出


(4)、一旦出现问题,检查步骤:
      1、telnet 127.0.0.1 1234
           telnet 127.0.0.1 5678
         如果不通,端口是否开启,linux下检查iptables
      2、如果是第三步返回fail,一般是uid用户连接断开了,只需要浏览器重新执行一下第二步js,在重新执行第三步

要回复问题请先登录注册