您当前的位置: 首页 > 慢生活 > 程序人生 网站首页程序人生
workerman接口listen实现内外部通信(管理后台或系统向websocket前端通信及websocket前端间互相通信)
发布时间:2021-12-02 23:14:50编辑:雪饮阅读()
最近用workerman开发一个医疗社交系统,原本以为是一个简单的用户间聊天的系统而已,但是没有想到系统中各种提示通知之类的也需要用websocket发送,并且不是用户间发送。
那么我的起初实现思路是用php模拟一个websocket客户端去负责发通知反馈之类的给这些用户,但实践过程发现workerman暂时没有比较好的websocket客户端模拟的方案,虽然能模拟,但是每次都要启动一个进程来模拟,这未免有点不太好吧,性能有点开销大吧,而且你还得使用php的微信函数exec,最后进程使用一次后你是不是还得关闭,否则就是进程浪费。可能还有更好的方案,总之感觉有点不太妥当。
那么其实对于这种需求场景,workerman还有另外一个虽然麻烦,但是感觉很权威的方案。
下面将来实践下,在用户间以及系统内部也就是说websocket协议与http协议之间的的通信方案。
php后端及时推送消息给客户端
原理:
1、建立一个websocket Worker,用来维持客户端长连接
2、websocket Worker内部建立一个text Worker
3、websocket Worker 与 text Worker是同一个进程,可以方便的共享客户端连接
4、某个独立的php后台系统通过text协议与text Worker通讯
5、text Worker操作websocket连接完成数据推送
首先我需要一个push.php用来实现websocket与内部的一个tcp协议共存的一个进程,则实例如:
push.php:
<?php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
// 初始化一个worker容器,监听1234端口
$worker = new Worker('websocket://0.0.0.0:8484');
/*
* 注意这里进程数必须设置为1,否则会报端口占用错误
* (php 7可以设置进程数大于1,前提是$inner_text_worker->reusePort=true)
*/
$worker->count = 1;
// worker进程启动后创建一个text Worker以便打开一个内部通讯端口
$worker->onWorkerStart = function($worker)
{
// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$inner_text_worker = new Worker('text://0.0.0.0:5678');
$inner_text_worker->onMessage = function(TcpConnection $connection, $buffer)
{
// $data数组格式,里面有to_uid,表示向那个uid的页面(前端websocket用户端)推送数据
$data = json_decode($buffer, true);
$uid = $data['to_uid'];
// 通过workerman,向uid的页面(前端websocket用户端)推送数据
$ret = sendMessageByUid($uid, $buffer);
// 返回推送结果给内部系统发起推送者
$response["msg"]=$ret ? 'ok' : 'fail';
$connection->send(json_encode($response));
};
// ## 执行监听 ##
$inner_text_worker->listen();
//内部通信worker监听完成
};
// 新增加一个属性,用来保存uid到connection的映射
$worker->uidConnections = [];
// 当有websocket客户端发来消息时执行的回调函数
$worker->onMessage = function(TcpConnection $connection, $data)
{
global $worker;
/*
* 建立uid到connection的映射(原本以为在onConnect时候至少能拿到get请求数据,原来onConnect除了通过$connection->getRemoteIp()获得对方ip,没有其他可以鉴别客户端的数据或者信息)
* */
$data=json_decode($data,true);
if($data["type"]=="buildConnect"){
$connection->uid=$data["uid"];
$worker->uidConnections[$connection->uid] = $connection;
}
else{
$to_uid=$data["to_uid"];
$ret=sendMessageByUid($to_uid,json_encode($data));
$response["msg"]=$ret ? 'ok' : 'fail';
$connection->send(json_encode($response));
}
};
// 当有客户端连接断开时
$worker->onClose = function(TcpConnection $connection)
{
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();
接下来需要一个inner.php模拟传统http栈内部的系统或某管理后台之类的要给某个用户发送消息(经过内部tcp协议转发)的脚本:
<?php
// 建立socket连接到内部推送端口
//参数1,错误号,参数2错误消息,参数3连接超时配置
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的数据,包含to_uid字段,表示是给这个uid推送
$data = ['type'=>'sendMessage', 'to_uid'=>2,"content"=>"2号你被系统禁用了!"];
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data)."\n");
// 读取推送结果
//参数2规定要读取的最大字节数。
echo fread($client, 8192);
然后我需要有两个客户端模拟两个用户进行websocket进行用户间通信:
客户端1:test.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="https://www.w3school.com.cn/jquery/jquery-1.11.1.min.js"></script>
<script type="">
$(document).ready(function(){
var ws = new WebSocket("ws://47.240.19.5:8484");
ws.onopen = function(){
var message={
type:"buildConnect",
uid:1
};
ws.send(JSON.stringify(message));
setTimeout(function(){
var message={
type:"sendMessage",
to_uid:2,
content:"2号你好"
};
ws.send(JSON.stringify(message));
},1000);
}
ws.onmessage = function(e){
console.log("接收到的数据:");
console.log(e.data);
}
ws.onclose = function(e){
}
ws.onerror = function(e){
}
});
</script>
</head>
<body>
</body>
</html>
客户端2:test2.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="https://www.w3school.com.cn/jquery/jquery-1.11.1.min.js"></script>
<script type="">
$(document).ready(function(){
var ws = new WebSocket("ws://47.240.19.5:8484");
ws.onopen = function(){
var message={
type:"buildConnect",
uid:2
};
ws.send(JSON.stringify(message));
setTimeout(function(){
var message={
type:"sendMessage",
to_uid:1,
content:"1号你好"
};
ws.send(JSON.stringify(message));
},1000);
}
ws.onmessage = function(e){
console.log("接收到的数据:");
console.log(e.data);
}
ws.onclose = function(e){
}
ws.onerror = function(e){
}
});
</script>
</head>
<body>
</body>
</html>
接下来就是整个方案的实际测试效果了:
首先我将用于websocket和内部通信tcp兼容的脚本push.php启动:
[root@izj6c2jeancylo0ppo4vz5z workerman]# php push.php start
Workerman[push.php] start in DEBUG mode
------------------------------------------- WORKERMAN --------------------------------------------
Workerman version:4.0.22 PHP version:7.0.33
-------------------------------------------- WORKERS ---------------------------------------------
proto user worker listen processes status
tcp root none websocket://0.0.0.0:8484 1 [OK]
--------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
然后当两个客户端都启动起来后客户端1可以收到客户端2的消息:

客户端2也不含糊的能收到客户端1的消息:

注意操作时要注意两个客户端都连接成功后各自刷新自己的页面就能给对方发消息了。
那么接下来我内部系统单独给客户端2再发一个消息:
[root@izj6c2jeancylo0ppo4vz5z workerman]# php inner.php
{"msg":"ok"}

关键字词:workerman,listen,websocket,推送,管理后台,后台