thinkphp5+GatewayWorker+Workerman

項目地址  ttps://www.workerman.net/workerman-chatjavascript

 

thinkphp5+GatewayWorker+Workerman聊天室,能夠多人聊天,指定某我的進行聊天,還能夠切換聊天房間
Windows版安裝
a) 安裝thinkphp5:php

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

b) 進入tp5的目錄,安裝Windows版本的workerman:
css

composer require workerman/workerman-for-win

c)安裝Windows版本的gateway:html

composer require workerman/gateway-worker-for-win

開始關鍵部分,服務端實現java

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

<?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\Sgatewaylinux

<?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\Sbusinessworkerweb

<?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\Eventsthinkphp

<?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));
       }
   }
  
}

 

代碼目錄截圖json

 

  

而後在項目根目錄 新增入口文件 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/

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息