ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

GatewayWorker 实现即时消息

2021-08-25 14:30:55  阅读:162  来源: 互联网

标签:即时消息 实现 self id client 10px var data GatewayWorker


GatewayWorker介绍:

GatewayWorker基于Workerman开发的一个项目框架,用于快速开发TCP长连接应用,例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等

GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接,并转发客户端的数据给BusinessWorker进程处理,BusinessWorker进程负责处理实际的业务逻辑(默认调用Events.php处理业务),并将结果推送给对应的客户端。Gateway服务和BusinessWorker服务可以分开部署在不同的服务器上,实现分布式集群。

GatewayWorker提供非常方便的API,可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器,也可以定时推送数据。

为什么使用GatewayWorker?

项目中需要做一个即时消息功能,研究过workerman也能实现即时消息,不过在多进程时,暂时没有什么好方法可以处理多进程之间进行通信,而GatewayWorker是在Workerman基础上开发的,多进程之间通信也解决了,并且可以处理较高的并发数。

1.下载及启动

http://www.workerman.net/download/GatewayWorker.zip

(1)解压缩GatewayWorker.zip

(2)进入GatewayWorker目录

(3)启动:

linux:

php start.php start

window:

双击 start_for_win.bat

2.修改服务端配置

修改start_gateway.php

<?php
/**
 * Gateway类用于初始化Gateway进程。
 * Gateway进程是暴露给客户端的让其连接的进程。所有客户端的请求都是由Gateway接收然后分发给BusinessWorker处理,
 * 同样BusinessWorker也会将要发给客户端的响应通过Gateway转发出去。
 * @document http://doc2.workerman.net
 */

use \Workerman\Worker;
use \GatewayWorker\Gateway;

// 自动加载类
require_once __DIR__ . '/../vendor/autoload.php';

// gateway进程 设置协议和端口
//$gateway = new Gateway("websocket://192.168.10.103:1234"); // 若非本机访问 可用相应内网ip
$gateway = new Gateway("websocket://0.0.0.0:1234");
// gateway名称,status方便查看
$gateway->name = 'solveGateway';
// gateway进程数
$gateway->count = 4;
// 本机ip,分布式部署时使用内网ip
$gateway->lanIp = '127.0.0.1';
// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000
// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口 
$gateway->startPort = 4000;
// 服务注册地址
$gateway->registerAddress = '127.0.0.1:1238';

// 心跳间隔
$gateway->pingInterval = 10;
// 心跳数据
$tiao =  [
    'type' => -1,
    'content' => '心跳检测'
];
$gateway->pingData = json_encode($tiao);

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

修改Events.php

<?php
/**
 * Events回调类 用于处理消息业务
 *
 * 主逻辑
 * 主要是处理 onConnect onMessage onClose 三个方法
 * onConnect 和 onClose 如果不需要可以不用实现并删除
 */

use \GatewayWorker\Lib\Gateway;


/*
 * 用于检测业务代码死循环或者长时间阻塞等问题
 * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
 * 然后观察一段时间workerman.log看是否有process_timeout异常
 */
// declare(ticks=1);

class Events
{
    protected static $userInfo = [];

    protected static $loginClientId = [];

    // 返回数据结构
    protected static $return = ['type' => 0, 'content' => ''];

    // 假数据 在项目可从数据库查询
    protected static $users = [
        '18101345114' => ['id' => 1, 'name' => '1号', 'phone' => '18101345114', 'token' => '60e7ef4d3e7e342255'],
        '18101345115' => ['id' => 2, 'name' => '2号', 'phone' => '18101345115', 'token' => '60f13533c298b73792'],
    ];

    // 假数据 在项目可从数据库查询
    protected static $users2 = [
        '1' => ['id' => 1, 'name' => '1号', 'phone' => '18101345114', 'token' => '60e7ef4d3e7e342255'],
        '2' => ['id' => 2, 'name' => '2号', 'phone' => '18101345115', 'token' => '60f13533c298b73792'],
    ];

    /**
     * 当客户端连接时触发
     * 只有一个请求头 没有携带其他信息
     * @param int $client_id 连接id
     * @throws Exception
     */
    public static function onConnect($client_id)
    {
        // 向当前client_id发送数据 
        //Gateway::sendToClient($client_id, "建立连接中...\r\n");
    }

    /**
     * 当客户端发来消息时触发
     * @param int $client_id 连接id
     * @param mixed $message 具体消息
     * @throws Exception
     */
    public static function onMessage($client_id, $message)
    {
        // 判断格式
        $data = json_decode($message, true);
        if (empty($data)) {
            Gateway::closeClient($client_id, '消息格式不对,走你');
        }
        if (!in_array($client_id, array_keys(self::$loginClientId))) {
            // 鉴权
            $phone = $data['phone'] ?? '';
            $token = $data['token'] ?? '';
            if (empty($data) || empty($phone) || empty($token) || !preg_match('/^[0-9]{11}$/i', $phone)) {
                self::writeRecordLog("1.鉴权有误:参数错误 " . var_export($data, true) . "\r\n");
                self::$return['content'] = '鉴权不符,走你';
                Gateway::closeClient($client_id, json_encode(self::$return));
            }
            // 查询是否有此用户
            $user = self::$users[$phone] ?? [];
            if (empty($user) || $user['token'] != $token) {
                self::writeRecordLog("2.没有该用户或token不正确\r\n");
                self::$return['content'] = '没有该用户或token不正确,走你';
                Gateway::closeClient($client_id, json_encode(self::$return));
            }
            // 连接意外中断后 会重新生成的client_id
            if(isset(self::$userInfo[$user['id']])) {
                self::$return['content'] = '关闭之前的连接';
                Gateway::closeClient(self::$userInfo[$user['id']], json_encode(self::$return));
            }
            // uid与client_id绑定
            Gateway::bindUid($client_id, $user['id']);
            // 记录登录信息
            self::$loginClientId[$client_id] = $user['id'];
            self::$userInfo[$user['id']] = $client_id;
            self::$return['content'] = '登录成功';
            Gateway::sendToClient($client_id, json_encode(self::$return));
            self::writeRecordLog($user['phone'] . "登录成功\r\n");
        } else {
            // 发送消息
            if (isset($data['toid'])) {
                $request_data = ['type' => 1, 'content' => $data['content'], 'time' => date('Y.m-d H:i')];
                switch($data['type']) {
                    case 1://点对点文本消息
                        $user = self::$users2[$data['fromid']] ?? [];
                        $request_data['fromName'] = $user['name'] ?? '未知';
                        break;
                }
                //判断是否登录
                if(Gateway::isUidOnline($data['toid']) && !empty($request_data['fromName'])) {
                    Gateway::sendToUid($data['toid'], json_encode($request_data));
                }
            }
        }
    }

    /**
     * 当用户断开连接时触发
     * @param int $client_id 连接id
     * @throws Exception
     */
    public static function onClose($client_id)
    {
        $uid = self::$loginClientId[$client_id];
        unset(self::$loginClientId[$client_id]);
        unset(self::$userInfo[$uid]);
    }

     /**
     * 日志记录
     * @param $content
     */
    protected static function writeRecordLog($content)
    {
        $filePath = self::createLogFile('workerMan.log', 'logs');
        if (is_array($content)) {
            $content = var_export($content, true);
        }
        file_put_contents($filePath, $content, FILE_APPEND);
    }

    /**
     * 创建日志文件||文件夹 方法
     * @param string $fileName
     * @param string $folder
     * @param string $Suffix
     * @return string
     */
    protected static function createLogFile($fileName, $folder = 'logs', $Suffix = null)
    {
        $path = dirname(__DIR__) . '/' . $folder;
        if (!file_exists($path)) {
            mkdir($path, 0755, true);
        }
        $filePath = rtrim($path, '/') . '/' . $fileName;
        // 处理在windows系统对文件读取时转换文件路径
        if (substr(php_uname(), 0, 1) == 'W') {
            $filePath = str_replace('/', '\\', $filePath);
        }
        if (!empty($Suffix)) {
            $filePath .= '.' . $Suffix;
        }
        if (!file_exists($filePath)) {
            $fp = fopen($filePath, "w+");
            fclose($fp);
        }
        return $filePath;
    }
}

 3.客户端网页

因为只是一个demo,没有做登录操作,故做了两个单网页通过websocket访问服务端

liao.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>聊天</title>
    <style type="text/css">
        .talk_con {
            width: 600px;
            height: 500px;
            border: 1px solid #666;
            margin: 50px auto 0;
            background: #f9f9f9;
        }

        .talk_show {
            width: 580px;
            height: 420px;
            border: 1px solid #666;
            background: #fff;
            margin: 10px auto 0;
            overflow: auto;
        }

        .talk_input {
            width: 580px;
            margin: 10px auto 0;
        }

        .whotalk {
            width: 100px;
            height: 30px;
            float: left;
            outline: none;
        }

        .talk_word {
            width: 400px;
            height: 26px;
            padding: 0px;
            float: left;
            margin-left: 10px;
            outline: none;
            text-indent: 10px;
        }

        .talk_sub {
            width: 56px;
            height: 30px;
            float: left;
            margin-left: 10px;
        }

        .atalk {
            margin: 10px;
        }

        .atalk span {
            display: inline-block;
            background: #0181cc;
            border-radius: 10px;
            color: #fff;
            padding: 5px 10px;
        }

        .btalk {
            margin: 10px;
            text-align: right;
        }

        .btalk span {
            display: inline-block;
            background: #ef8201;
            border-radius: 10px;
            color: #fff;
            padding: 5px 10px;
        }
    </style>
</head>
<body>
<div class="talk_con">
    <div class="talk_show" id="words"></div>
    <div class="talk_input">
        <select class="whotalk" id="toid">
            <option value="2">2号</option>
        </select>
        <input type="text" class="talk_word" id="talkwords">
        <input type="button" value="发送" class="talk_sub" id="talksub">
    </div>
</div>
<script src="../../jquery-3.2.1.js"></script>
<script type="text/javascript">
    //var ws = new WebSocket('ws://192.168.10.103:1234');
    var ws = new WebSocket('ws://127.0.0.1:1234');
    ws.onopen = function () {
        var login = '{"phone":"18101345114","token":"60e7ef4d3e7e342255"}';
        ws.send(login);
    };
    ws.onmessage = function (e) {
        var msg = JSON.parse(e.data);
        var sender = '系统消息:';
        switch (msg.type) {
            case 1:
                sender = msg.fromName + ':';
                break;
            case 2:
                sender = '群消息:';
                return;
        }
        var data = sender + msg.content;
        if (msg.type != -1) {
            listMsg('<div class="atalk"><span>' + data + '</span></div>');
        }
    };

    $("#talksub").click(function () {
        var cont = $("#talkwords").val();
        if (cont == "") {
            alert("消息不能为空");
            return;
        }
        var toid = $("#toid").val();
        var data = '{"toid":"' + toid + '","fromid":"1","type":"1","content":"' + cont + '"}';
        ws.send(data);
        $("#talkwords").val('');
        listMsg('<div class="btalk"><span>1号:' + cont + '</span></div>');
    });

    /**
     * 将消息内容添加到输出框中,并将滚动条滚动到最下方
     */
    function listMsg(data) {
        var msg_list = document.getElementById("words");
        msg_list.innerHTML = msg_list.innerHTML + data;
        msg_list.scrollTop = msg_list.scrollHeight;
    }
</script>
</body>
</html>

liao2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>聊天2</title>
    <style type="text/css">
        .talk_con {
            width: 600px;
            height: 500px;
            border: 1px solid #666;
            margin: 50px auto 0;
            background: #f9f9f9;
        }

        .talk_show {
            width: 580px;
            height: 420px;
            border: 1px solid #666;
            background: #fff;
            margin: 10px auto 0;
            overflow: auto;
        }

        .talk_input {
            width: 580px;
            margin: 10px auto 0;
        }

        .whotalk {
            width: 100px;
            height: 30px;
            float: left;
            outline: none;
        }

        .talk_word {
            width: 400px;
            height: 26px;
            padding: 0px;
            float: left;
            margin-left: 10px;
            outline: none;
            text-indent: 10px;
        }

        .talk_sub {
            width: 56px;
            height: 30px;
            float: left;
            margin-left: 10px;
        }

        .atalk {
            margin: 10px;
        }

        .atalk span {
            display: inline-block;
            background: #0181cc;
            border-radius: 10px;
            color: #fff;
            padding: 5px 10px;
        }

        .btalk {
            margin: 10px;
            text-align: right;
        }

        .btalk span {
            display: inline-block;
            background: #ef8201;
            border-radius: 10px;
            color: #fff;
            padding: 5px 10px;
        }
    </style>
</head>
<body>
<div class="talk_con">
    <div class="talk_show" id="words"></div>
    <div class="talk_input">
        <select class="whotalk" id="toid">
            <option value="1">1号</option>
        </select>
        <input type="text" class="talk_word" id="talkwords">
        <input type="button" value="发送" class="talk_sub" id="talksub">
    </div>
</div>
<script src="../../jquery-3.2.1.js"></script>
<script type="text/javascript">
    //var ws = new WebSocket('ws://192.168.10.103:1234');
    var ws = new WebSocket('ws://127.0.0.1:1234');
    ws.onopen = function () {
        var login = '{"phone":"18101345115","token":"60f13533c298b73792"}';
        ws.send(login);
    };
    ws.onmessage = function (e) {
        var msg = JSON.parse(e.data);
        var sender = '系统消息:';
        switch (msg.type) {
            case 1:
                sender = msg.fromName + ':';
                break;
            case 2:
                sender = '群消息:';
                return;
        }
        var data = sender + msg.content;
        if (msg.type != -1) {
            listMsg('<div class="atalk"><span>' + data + '</span></div>');
        }
    };

    $("#talksub").click(function () {
        var cont = $("#talkwords").val();
        if (cont == "") {
            alert("消息不能为空");
            return;
        }
        var toid = $("#toid").val();
        var data = '{"toid":"' + toid + '","fromid":"2","type":"1","content":"' + cont + '"}';
        ws.send(data);
        $("#talkwords").val('');
        listMsg('<div class="btalk"><span>2号:' + cont + '</span></div>');
    });

    /**
     * 将消息内容添加到输出框中,并将滚动条滚动到最下方
     */
    function listMsg(data) {
        var msg_list = document.getElementById("words");
        msg_list.innerHTML = msg_list.innerHTML + data;
        msg_list.scrollTop = msg_list.scrollHeight;
    }
</script>
</body>
</html>

4.启动后,进行聊天

服务端启动

 浏览器访问

 比较简单的就实现了互相通信聊天!

标签:即时消息,实现,self,id,client,10px,var,data,GatewayWorker
来源: https://blog.csdn.net/json_ligege/article/details/119909745

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有