首页 > Web开发 > 详细

thinkphp5+GatewayWorker+Workerman

时间:2019-07-29 19:01:18      阅读:121      评论:0      收藏:0      [点我收藏+]

项目地址  ttps://www.workerman.net/workerman-chat

 

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;

d): 由于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/

  

 

thinkphp5+GatewayWorker+Workerman

原文:https://www.cnblogs.com/SofuBlue/p/11265489.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!