PHP下workerman实现消息推送

一个简易的消息推送,直接上代码:

开启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>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值