(转)thinkphp5+GatewayWorker+Workerman

发布时间:2019-03-21 11:34:34编辑:文明阅读(904)

    文章转自:http://www.thinkphp.cn/code/3937.html


    thinkphp5+GatewayWorker+Workerman聊天室,可以多人聊天,指定某个人进行聊天,还可以切换聊天房间

    Windows版安装

    a) 安装thinkphp5;

    composer create-project topthink/think tp5  --prefer-dist

    复制代码

    b) 进入tp5的目录,安装Windows版本的workerman;

    composer require workerman/workerman-for-win

    复制代码

    c} 安装Windows版本的gateway;

    composer require workerman/gateway-worker-for-win

    复制代码

    开始关键部分,服务端实现


    控制器 控制器:app\index\controller\Sregister

    <?php

    namespace app\index\controller;

    use Workerman\Worker;

    use GatewayWorker\Register;

    class Sregister{

        public function __construct(){

            // register 服务必须是text协议

            $register = new Register('text://0.0.0.0:1236');

            

            // 如果不是在根目录启动,则运行runAll方法

            if(!defined('GLOBAL_START'))

            {

                Worker::runAll();

            }

        }

    }

    复制代码

    控制器:app\index\controller\Sgateway

    <?php

    namespace app\index\controller;

    use Workerman\Worker;

    use GatewayWorker\Gateway;

    use Workerman\Autoloader;

    class Sgateway{

        public function __construct(){

            // gateway 进程

            $gateway = new Gateway("Websocket://0.0.0.0:7272");

            // 设置名称,方便status时查看

            $gateway->name = 'ChatGateway';

            // 设置进程数,gateway进程数建议与cpu核数相同

            $gateway->count = 4;

            // 分布式部署时请设置成内网ip(非127.0.0.1)

            $gateway->lanIp = '127.0.0.1';

            // 内部通讯起始端口,假如$gateway->count=4,起始端口为4000

            // 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口

            $gateway->startPort = 2300;

            // 心跳间隔

            $gateway->pingInterval = 10;

            // 心跳数据

            $gateway->pingData = '{"type":"ping"}';

            // 服务注册地址

            $gateway->registerAddress = '127.0.0.1:1236';

            

            /*

             // 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调

             $gateway->onConnect = function($connection)

             {

             $connection->onWebSocketConnect = function($connection , $http_header)

             {

             // 可以在这里判断连接来源是否合法,不合法就关掉连接

             // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接

             if($_SERVER['HTTP_ORIGIN'] != 'http://chat.workerman.net')

             {

             $connection->close();

             }

             // onWebSocketConnect 里面$_GET $_SERVER是可用的

             // var_dump($_GET, $_SERVER);

             };

             };

             */

            

            // 如果不是在根目录启动,则运行runAll方法

            if(!defined('GLOBAL_START'))

            {

                Worker::runAll();

            }

            

            

        }

    }

    复制代码

    控制器:app\index\controller\Sbusinessworker

    <?php

    namespace app\index\controller;

    use Workerman\Worker;

    use GatewayWorker\BusinessWorker;

    use Workerman\Autoloader;

    class Sbusinessworker{

        public function __construct(){

            // bussinessWorker 进程

            $worker = new BusinessWorker();

            // worker名称

            $worker->name = 'ChatBusinessWorker';

            // bussinessWorker进程数量

            $worker->count = 4;

            // 服务注册地址

            $worker->registerAddress = '127.0.0.1:1236';

            //设置处理业务的类,此处制定Events的命名空间

            $worker->eventHandler = 'app\index\controller\Events';

            

            // 如果不是在根目录启动,则运行runAll方法

            if(!defined('GLOBAL_START'))

            {

                Worker::runAll();

            }

        }

    }

    复制代码

    控制器:app\index\controller\Events

    <?php

    namespace app\index\controller;

    /**

     * 用于检测业务代码死循环或者长时间阻塞等问题

     * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload

     * 然后观察一段时间workerman.log看是否有process_timeout异常

     */

    //declare(ticks=1);

    /**

     * 聊天主逻辑

     * 主要是处理 onMessage onClose 

     */

    use \GatewayWorker\Lib\Gateway;

    class Events

    {

       /**

        * 有消息时

        * @param int $client_id

        * @param mixed $message

        */

       public static function onMessage($client_id, $message)

       {

            // debug

            echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']}  client_id:$client_id session:".json_encode($_SESSION)." onMessage:".$message."\n";

            

            // 客户端传递的是json数据

            $message_data = json_decode($message, true);

            if(!$message_data)

            {

                return ;

            }

            

            // 根据类型执行不同的业务

            switch($message_data['type'])

            {

                // 客户端回应服务端的心跳

                case 'pong':

                    return;

                // 客户端登录 message格式: {type:login, name:xx, room_id:1} ,添加到客户端,广播给所有客户端xx进入聊天室

                case 'login':

                    // 判断是否有房间号

                    if(!isset($message_data['room_id']))

                    {

                        throw new \Exception("\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");

                    }

                    

                    // 把房间号昵称放到session中

                    $room_id = $message_data['room_id'];

                    $client_name = htmlspecialchars($message_data['client_name']);

                    $_SESSION['room_id'] = $room_id;

                    $_SESSION['client_name'] = $client_name;

                  

                    // 获取房间内所有用户列表 

                    $clients_list = Gateway::getClientSessionsByGroup($room_id);

                    foreach($clients_list as $tmp_client_id=>$item)

                    {

                        $clients_list[$tmp_client_id] = $item['client_name'];

                    }

                    $clients_list[$client_id] = $client_name;

                    

                    // 转播给当前房间的所有客户端,xx进入聊天室 message {type:login, client_id:xx, name:xx} 

                    $new_message = array('type'=>$message_data['type'], 'client_id'=>$client_id, 'client_name'=>htmlspecialchars($client_name), 'time'=>date('Y-m-d H:i:s'));

                    Gateway::sendToGroup($room_id, json_encode($new_message));

                    Gateway::joinGroup($client_id, $room_id);

                   

                    // 给当前用户发送用户列表 

                    $new_message['client_list'] = $clients_list;

                    Gateway::sendToCurrentClient(json_encode($new_message));

                    return;

                    

                // 客户端发言 message: {type:say, to_client_id:xx, content:xx}

                case 'say':

                    // 非法请求

                    if(!isset($_SESSION['room_id']))

                    {

                        throw new \Exception("\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}");

                    }

                    $room_id = $_SESSION['room_id'];

                    $client_name = $_SESSION['client_name'];

                    

                    // 私聊

                    if($message_data['to_client_id'] != 'all')

                    {

                        $new_message = array(

                            'type'=>'say',

                            'from_client_id'=>$client_id, 

                            'from_client_name' =>$client_name,

                            'to_client_id'=>$message_data['to_client_id'],

                            'content'=>"<b>对你说: </b>".nl2br(htmlspecialchars($message_data['content'])),

                            'time'=>date('Y-m-d H:i:s'),

                        );

                        Gateway::sendToClient($message_data['to_client_id'], json_encode($new_message));

                        $new_message['content'] = "<b>你对".htmlspecialchars($message_data['to_client_name'])."说: </b>".nl2br(htmlspecialchars($message_data['content']));

                        return Gateway::sendToCurrentClient(json_encode($new_message));

                    }

                    

                    $new_message = array(

                        'type'=>'say', 

                        'from_client_id'=>$client_id,

                        'from_client_name' =>$client_name,

                        'to_client_id'=>'all',

                        'content'=>nl2br(htmlspecialchars($message_data['content'])),

                        'time'=>date('Y-m-d H:i:s'),

                    );

                    return Gateway::sendToGroup($room_id ,json_encode($new_message));

            }

       }

       

       /**

        * 当客户端断开连接时

        * @param integer $client_id 客户端id

        */

       public static function onClose($client_id)

       {

           // debug

           echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']}  client_id:$client_id onClose:''\n";

           

           // 从房间的客户端列表中删除

           if(isset($_SESSION['room_id']))

           {

               $room_id = $_SESSION['room_id'];

               $new_message = array('type'=>'logout', 'from_client_id'=>$client_id, 'from_client_name'=>$_SESSION['client_name'], 'time'=>date('Y-m-d H:i:s'));

               Gateway::sendToGroup($room_id, json_encode($new_message));

           }

       }

      

    }

    复制代码

    代码目录截图



    然后在项目根目录 新增入口文件 start_register.php 、start_gateway.php 、start_businessworker.php三个入口文件


    文件:start_register.php

    <?php 

    define('APP_PATH', __DIR__ . '/application/');

    define('BIND_MODULE','index/Sregister');

    // 加载框架引导文件

    require __DIR__ . '/thinkphp/start.php';

    复制代码

    文件:start_gateway.php

    <?php

    define('APP_PATH', __DIR__ . '/application/');

    define('BIND_MODULE','index/Sgateway');

    // 加载框架引导文件

    require __DIR__ . '/thinkphp/start.php';

    复制代码

    文件:start_businessworker.php

    <?php

    define('APP_PATH', __DIR__ . '/application/');

    define('BIND_MODULE','index/Sbusinessworker');

    // 加载框架引导文件

    require __DIR__ . '/thinkphp/start.php';

    复制代码

    [b]由于PHP-CLI在windows系统无法实现多进程以及守护进程,所以只能把三个文件放到bat文件,然后双击启动


    bat文件:start_for_win.bat

    php start_register.php start_gateway.php start_businessworker.phppause

    复制代码

    代码目录截图



    启动程序


    在项目的根目录下双击启动 start_for_win.bat



    服务端实现

    view : tp5\application\index\view\index.html

    <html><head>

      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

      <title>workerman-chat PHP聊天室 Websocket(HTLM5/Flash)+PHP多进程socket实时推送技术</title>

      <script type="text/javascript">

      //WebSocket = null;

      </script>

      <link href="__PUBLIC__/chat/css/bootstrap.min.css" rel="stylesheet">

      <link href="__PUBLIC__/chat/css/style.css" rel="stylesheet">

      <!-- Include these three JS files: -->

      <script type="text/javascript" src="__PUBLIC__/chat/js/swfobject.js"></script>

      <script type="text/javascript" src="__PUBLIC__/chat/js/web_socket.js"></script>

      <script type="text/javascript" src="__PUBLIC__/chat/js/jquery.min.js"></script>

      <script type="text/javascript">

        if (typeof console == "undefined") {    this.console = { log: function (msg) {  } };}

        // 如果浏览器不支持websocket,会使用这个flash自动模拟websocket协议,此过程对开发者透明

        WEB_SOCKET_SWF_LOCATION = "__PUBLIC__/chat/swf/WebSocketMain.swf";

        // 开启flash的websocket debug

        WEB_SOCKET_DEBUG = true;

          

        var ws, name, client_list={};

        // 连接服务端

        function connect() {

           // 创建websocket

           ws = new WebSocket("ws://"+document.domain+":7272");

           // 当socket连接打开时,输入用户名

           ws.onopen = onopen;

           // 当有消息时根据消息类型显示不同信息

           ws.onmessage = onmessage; 

           ws.onclose = function() {

              console.log("连接关闭,定时重连");

              connect();

           };

           ws.onerror = function() {

               console.log("出现错误");

           };

        }

        // 连接建立时发送登录信息

        function onopen()

        {

            if(!name)

            {

                show_prompt();

            }

            // 登录

            var login_data = '{"type":"login","client_name":"'+name.replace(/"/g, '\\"')+'","room_id":"<?php echo isset($_GET['room_id']) ? $_GET['room_id'] : 1?>"}';

            console.log("websocket握手成功,发送登录数据:"+login_data);

            ws.send(login_data);

        }

        // 服务端发来消息时

        function onmessage(e)

        {

            console.log(e.data);

            var data = eval("("+e.data+")");

            switch(data['type']){

                // 服务端ping客户端

                case 'ping':

                    ws.send('{"type":"pong"}');

                    break;;

                // 登录 更新用户列表

                case 'login':

                    //{"type":"login","client_id":xxx,"client_name":"xxx","client_list":"[...]","time":"xxx"}

                    say(data['client_id'], data['client_name'],  data['client_name']+' 加入了聊天室', data['time']);

                    if(data['client_list'])

                    {

                        client_list = data['client_list'];

                    }

                    else

                    {

                        client_list[data['client_id']] = data['client_name']; 

                    }

                    flush_client_list();

                    console.log(data['client_name']+"登录成功");

                    break;

                // 发言

                case 'say':

                    //{"type":"say","from_client_id":xxx,"to_client_id":"all/client_id","content":"xxx","time":"xxx"}

                    say(data['from_client_id'], data['from_client_name'], data['content'], data['time']);

                    break;

                // 用户退出 更新用户列表

                case 'logout':

                    //{"type":"logout","client_id":xxx,"time":"xxx"}

                    say(data['from_client_id'], data['from_client_name'], data['from_client_name']+' 退出了', data['time']);

                    delete client_list[data['from_client_id']];

                    flush_client_list();

            }

        }

        // 输入姓名

        function show_prompt(){  

            name = prompt('输入你的名字:', '');

            if(!name || name=='null'){  

                name = '游客';

            }

        }

        // 提交对话

        function onSubmit() {

          var input = document.getElementById("textarea");

          var to_client_id = $("#client_list option:selected").attr("value");

          var to_client_name = $("#client_list option:selected").text();

          ws.send('{"type":"say","to_client_id":"'+to_client_id+'","to_client_name":"'+to_client_name+'","content":"'+input.value.replace(/"/g, '\\"').replace(/\n/g,'\\n').replace(/\r/g, '\\r')+'"}');

          input.value = "";

          input.focus();

        }

        // 刷新用户列表框

        function flush_client_list(){

            var userlist_window = $("#userlist");

            var client_list_slelect = $("#client_list");

            userlist_window.empty();

            client_list_slelect.empty();

            userlist_window.append('<h4>在线用户</h4><ul>');

            client_list_slelect.append('<option value="all" id="cli_all">所有人</option>');

            for(var p in client_list){

                userlist_window.append('<li id="'+p+'">'+client_list[p]+'</li>');

                client_list_slelect.append('<option value="'+p+'">'+client_list[p]+'</option>');

            }

            $("#client_list").val(select_client_id);

            userlist_window.append('</ul>');

        }

        // 发言

        function say(from_client_id, from_client_name, content, time){

            $("#dialog").append('<div class="speech_item"><img src="http://lorempixel.com/38/38/?'+from_client_id+'" class="user_icon" /> '+from_client_name+' <br> '+time+'<div style="clear:both;"></div><p class="triangle-isosceles top">'+content+'</p> </div>');

        }

        $(function(){

            select_client_id = 'all';

            $("#client_list").change(function(){

                 select_client_id = $("#client_list option:selected").attr("value");

            });

        });

      </script>

    </head>

    <body onload="connect();">

        <div class="container">

            <div class="row clearfix">

                <div class="col-md-1 column">

                </div>

                <div class="col-md-6 column">

                   <div class="thumbnail">

                       <div class="caption" id="dialog"></div>

                   </div>

                   <form onsubmit="onSubmit(); return false;">

                        <select style="margin-bottom:8px" id="client_list">

                            <option value="all">所有人</option>

                        </select>

                        <textarea class="textarea thumbnail" id="textarea"></textarea>

                        <div class="say-btn"><input type="submit" class="btn btn-default" value="发表" /></div>

                   </form>

                   <div>

                       <b>房间列表:</b>(当前在 房间<?php echo isset($_GET['room_id'])&&intval($_GET['room_id'])>0 ? intval($_GET['room_id']):1; ?>)<br>

                       <a href="/?room_id=1">房间1</a>    <a href="/?room_id=2">房间2</a>    <a href="/?room_id=3">房间3</a>    <a href="/?room_id=4">房间4</a>

                   <br><br>

                   </div>

                   <p class="cp">PHP多进程+Websocket(HTML5/Flash)+PHP Socket实时推送技术    Powered by <a href="http://www.workerman.net/workerman-chat" target="_blank">workerman-chat</a></p>

                </div>

                <div class="col-md-3 column">

                   <div class="thumbnail">

                       <div class="caption" id="userlist"></div>

                   </div>

                  

                </div>

            </div>

        </div>

        <script type="text/javascript">var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F7b1919221e89d2aa5711e4deb935debd' type='text/javascript'%3E%3C/script%3E"));</script>

    </body>

    </html>

    复制代码

    运行结果截图



    linux版安装

    a) 安装thinkphp5;

    composer create-project topthink/think tp5  --prefer-dist

    复制代码

    b) 进入tp5的目录,安装linux版本的workerman;

    composer require topthink/think-worker

    复制代码

    c} 安装linux版本的gateway;

    composer require workerman/gateway-worker-for-win

    复制代码

    关键部分,服务端实现

    复制代码

    控制器 app\index\controller\Gate

    <?php 

    /**

     * linux workerman例子测试

     * 需要在Linux系统控制台进行启动,启动文件位于根目录的start.php文件中

     * Windows无法进行同时启动多个协议

     * 由于PHP-CLI在windows系统无法实现多进程以及守护进程,所以windows版本Workerman建议仅作开发调试使用。

     */

    namespace app\index\controller;

    use Workerman\Worker;

    use GatewayWorker\Gateway;

    use GatewayWorker\Register;

    use GatewayWorker\BusinessWorker;

    class Gate

    {

        /**

         * 构造函数

         * @access public

         */

        public function __construct(){

            

            //初始化各个GatewayWorker

            //初始化register register 服务必须是text协议

            $register = new Register('text://0.0.0.0:1236');

        

            //初始化 bussinessWorker 进程

            $worker = new BusinessWorker();

            // worker名称

            $worker->name = 'ChatBusinessWorker';

            // bussinessWorker进程数量

            $worker->count = 4;

            // 服务注册地址

            $worker->registerAddress = '127.0.0.1:1236';

            //设置处理业务的类,此处制定Events的命名空间

            $worker->eventHandler = 'app\index\controller\Events';

            // 初始化 gateway 进程

            $gateway = new Gateway("websocket://0.0.0.0:7272");

            // 设置名称,方便status时查看

            $gateway->name = 'ChatGateway';

            $gateway->count = 4;

            // 分布式部署时请设置成内网ip(非127.0.0.1)

            $gateway->lanIp = '127.0.0.1';

            // 内部通讯起始端口,假如$gateway->count=4,起始端口为4000

            // 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口

            $gateway->startPort = 2300;

            // 心跳间隔

            $gateway->pingInterval = 10;

            // 心跳数据

            $gateway->pingData = '{"type":"ping"}';

            // 服务注册地址

            $gateway->registerAddress = '127.0.0.1:1236';

        

            //运行所有Worker;

            Worker::runAll();

        }

    }

    复制代码

    入口文件

    文件: start.php

    <?php

    /**

     * workerman + GatewayWorker

     * 此文件只能在Linux运行

     * run with command

     * php start.php start

     */

    ini_set('display_errors', 'on');

    if(strpos(strtolower(PHP_OS), 'win') === 0)

    {

        exit("start.php not support windows.\n");

    }

    //检查扩展

    if(!extension_loaded('pcntl'))

    {

        exit("Please install pcntl extension. See http://doc3.workerman.net/appendices/install-extension.html\n");

    }

    if(!extension_loaded('posix'))

    {

        exit("Please install posix extension. See http://doc3.workerman.net/appendices/install-extension.html\n");

    }

    define('APP_PATH', __DIR__ . '/application/');

    define('BIND_MODULE','chat/Gate');

    // 加载框架引导文件

    require __DIR__ . '/thinkphp/start.php';

    复制代码

    启动程序

    php start.php start

    复制代码

    客户端跟Windows一样就可以了


    workerman官网:http://www.workerman.net/

    workerman文档:http://doc3.workerman.net/

    GatewayWorker文档:http://doc3.workerman.net/


    还没有小伙伴评论,快来抢沙发啦~~!