一个简易的消息推送,直接上代码:
开启worker服务后,客户端连接时发送用户id至服务端并以对象的结构赋值在worker实例中,在worker建立的text协议连接内部端口用来模拟客户端发消息至服务端
服务端代码:
PushService.php
<?php
/**
* author:
* CSDN qq_41991657
*/
$rootPath = realpath(dirname(__FILE__) . '/../');
require_once $rootPath . '/vendor/autoload.php';
require_once $rootPath . '/vendor/workerman/workerman/Autoloader.php';
use Workerman\Worker;
use Workerman\Lib\Timer;
//创建wss
$context = array(
'ssl' => array(
// 请使用绝对路径
'local_cert' => '/data/ssl/1.crt', //或pem文件
'local_pk' => '/data/ssl/2.key',
'verify_peer' => false,
// 'allow_self_signed' => true, //如果是自签名证书需要开启此选项
)
);
// 初始化一个worker容器,监听8083端口
// 这里设置的是websocket协议(端口任意,但是需要保证没被其它程序占用)
$worker = new Worker('websocket://0.0.0.0:8083'/*, $context*/);//开启ssl需$context
// 设置transport开启ssl,websocket+ssl即wss
//$worker->transport = 'ssl';
// 守护进程启动
$worker::$daemonize = true;
//调试输出用户uid到日志
//Worker::$logFile = $rootPath . '/runtime/logs/workerman.log';
/*
* 注意这里进程数必须设置为1,否则会报端口占用错误
* (php 7可以设置进程数大于1,前提是$inner_text_worker->reusePort=true)
*/
$worker->count = 1;
$worker->name = 'PushService';
// 新增加一个属性,用来保存uid到connection的映射
$worker->uidConnections = array();
//$worker->onConnect = function () {
// //在线人数统计
// global $connectionCount;
// ++$connectionCount;
//};
// 当有客户端发来消息时执行的回调函数
$worker->onMessage = function ($connection, $data) {
$connection->lastMessageTime = time();
global $worker;
// 判断当前客户端是否已经验证,既是否设置了uid
if (!isset($connection->uid)) {
// 没验证的话把第一个包当做uid
$connection->uid = $data;
/* 保存uid到connection的映射,这样可以方便的通过uid查找connection,
* 实现针对特定uid推送数据
*/
$worker->uidConnections[$connection->uid] = $connection;
//print_r(array_keys($worker->uidConnections));
return;
}
};
//心跳
$worker->onWorkerStart = function ($worker) {
Timer::add(1, function () use ($worker) {
$time_now = time();
foreach ($worker->connections as $connection) {
// global $connectionCount;
// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
if ($time_now - $connection->lastMessageTime > 55) {
$connection->close();
}
$connection->send('ping');
}
});
// worker进程启动后创建一个text Worker以便打开一个内部通讯端口
// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$inner_text_worker = new Worker('text://0.0.0.0:5679');
// 多个进程监听同一个端口(监听套接字不是继承自父进程)提升多进程短连接应用的性能
$inner_text_worker->reusePort = true;
$inner_text_worker->onMessage = function ($connection, $buffer) {
// $data数组格式,里面有uid,表示向那个uid的页面推送数据
$data = json_decode($buffer, true);
$uidList = $data['uid'];
if (!empty($uidList)) {
foreach ($uidList as $uid) {
// 通过workerman,向uid的页面推送数据
$ret = sendMessageByUid($uid, $buffer);
}
} else {
$ret = broadcast($buffer);
}
// 返回推送结果
$connection->send($ret ? 'true' : 'false');
};
// ## 执行监听 ##
$inner_text_worker->listen();
};
// 当有客户端连接断开时
$worker->onClose = function ($connection) {
global $worker;
// print_r(array_keys($worker->uidConnections));
// global $connectionCount;
if (isset($connection->uid)) {
// $connectionCount--;
// 连接断开时删除映射
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 PushService.php start
模拟客户端推送消息服务端:
/**
* author:
* CSDN qq_41991657
*/
class ActivePushMessage
{
/**
* @brief 即时通讯
* @method POST
* @param int uid uid
* @param string sid sid
* @param string message 消息
* @param int uidList 选择推送用户[](为空则全体推送)
* @return bool
* @loginRequired
*/
public function run()
{
//$message = \Yii::$app->request->post('message');
//$uidList = \Yii::$app->request->post('uidList');//[123456,456789]
// 建立socket连接到内部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5679', $errno, $errmsg, 1);
// 推送的数据,包含uid字段,表示是给这个uid推送
//$uid = empty($uidList) ? 0 : json_decode($uidList, true);
$data = [
'uid' => [123456],//$uid
'message' => [
'styleContent' => '你好',//$message
'jump' => 0,
'categoryId' => 0,
]
];
// 发送数据,注意5679端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data) . "\n");
// 读取推送结果
// echo fread($client, 8192);
return fread($client, 8192) ? true : false;
}
}
客户端前端代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<title>Document</title>
</head>
<body>
<div>
uid:<input type="text" value="123456">
</div>
<text id="content" class="message-item">
</text>
<script>
ws = new WebSocket("ws://127.0.0.1:8083");
var uid = document.getElementsByTagName('input')[0]
ws.onopen = function () {
var val = uid.value
console.log('连接成功');
ws.send(val)
setInterval(function () {
ws.send(val)
}, 1 * 30 * 1000)
};
ws.onmessage = function (e) {
if (typeof e.data == "string" && e.data != 'ping') {
console.log(e.data)
var data = JSON.parse(e.data)
if (data['message']['styleContent'] != '') {
$(".message-item").append("<div>" + "公告:" + data['message']['styleContent'] + "</div>")
}
}
};
</script>
</body>
</html>