1. 程式人生 > >聊天室(下篇)GatewayWorker 與 Laravel 的整合

聊天室(下篇)GatewayWorker 與 Laravel 的整合

思路

上一篇大概梳理了一下 GatewayWorker 的基礎知識。這篇就來準備整合 GatewayWorker 到 Laravel。

GatewayWorker 是基於 Socket 監聽的伺服器框架,而 Laravel 是基於 HTTP 請求/響應模型的 Web 框架。所以一定要明白,兩者的部署是獨立分開互不干擾的。

因此在物理上它們的整合方式就見仁見智了。而官方, walkor 大神(GatewayWorker 框架作者)在手冊裡也給出了與 MVC 框架的結合方式,如下圖所示:

客戶端瀏覽器建立與 GatewayWorker 的 WebSocket 連線,所有的業務邏輯由客戶端通過 HTTP 協議 GET/POST 到 Web 框架,由 Web 框架統一處理。僅當客戶端瀏覽器需要主動推送資料時, Web 框架將呼叫 GatewayWorker 提供的 API(

GatewayClient),由 GatewayWorker 通過 WebSocket 主動推送給客戶端。

步驟

大致的思路有了,具體的實現步驟配合聊天室部分程式碼,如下所示。

首先要定製 GatewayWorker 的所有程序,並把它整合到 laravel 專案根目錄下的 gatewayworker 目錄,該目錄的結構遵循了官方推薦:

gatewayworker/
├── app
│   └── chat
│       ├── Events.php                 # BusinessWorker 程序的實際業務處理類
│       ├── start_businessworker.php   # BusinessWorker 程序的啟動檔案
│       ├── start_gateway.php          # Gateway 程序的啟動檔案
│       └── start_register.php         # Register 服務程序的啟動檔案
├── composer.json
├── composer.lock
├── start_for_win.bat                  # Windows 環境下 GatewayWorker 所有 Worker 程序的啟動檔案
├── start.php                          # Linux 環境下 GatewayWorker 所有 Worker 程序的啟動檔案
└── vendor

整個 gatewayworker/ 目錄的結構和原始碼幾乎與官方的 workerman-chat 一模一樣。僅有 Events.php 略有不同。

start.php 負責啟動所有的 worker 程序:

// 載入所有applications/*/start.php,以便啟動所有服務
foreach(glob(__DIR__.'/app/*/start*.php') as $start_file) {
    require_once $start_file;
}

// 執行所有服務
Worker::runAll();

start_register.php 負責啟動 Register 程序,監聽本機 1238 埠,以便 Gateway 程序與 BuisnessWorker 程序建立通訊。

$register = new Register('text://0.0.0.0:1238');   // 必須text協議

start_gateway.php 負責啟動 Gateway 程序,監聽客戶端在 7272 埠、基於 WebSocket 協議的連線和連線上的資料。同時設定程序名、程序數、lanIp、起始埠和心跳檢測,以及註冊通訊地址。

$gateway = new Gateway("Websocket://0.0.0.0:7272");
$gateway->name = 'ChatGateway';
$gateway->count = 4;
$gateway->lanIp = '127.0.0.1'; // 分散式部署時要填寫真實IP(非127.0.0.1)
$gateway->startPort = 2300;
$gateway->pingInterval = 10;   // 設定心跳,防止長時間不通訊被路由節點強行斷開
$gateway->pingData = '{"type":"ping"}';
$gateway->registerAddress = '127.0.0.1:1238'; // 用於和BusinessWorker程序通訊,與Gateway程序的註冊地址保持一致

start_businessworker.php 負責啟動 BusinessWorker 程序,設定程序名、程序數,以及註冊通訊地址。

$worker = new BusinessWorker();
$worker->name = 'ChatBusinessWorker';
$worker->count = 4;
$worker->registerAddress = '127.0.0.1:1238'; // 用於和Gateway程序通訊,與Gateway程序的註冊地址保持一致

Events.php 中只負責兩件事:

第一件、當客戶端建立連線時,在 onConnect 回撥中把 client_id 傳送給客戶端(讓客戶端通過 AJAX 請求 Web 框架去繫結 uid 與 client_id)。

第二件、當客戶端連線關閉時,在 onClose 回撥中通過該 client_id 獲取該使用者所在的 room_id(聊天室唯一 ID,由 Laravel 生成),並使用 Gateway::sendToGroup($group) 向該房間推送一個客戶離開訊號(客戶端收到這個訊號會發起 AJAX 請求向 Web 框架請求最新的使用者列表)。

而 onMessage 回撥留空即可。前面已經說過,我們所有的業務邏輯,都儘量在 Web 框架裡實現。GatewayWorker 只提供 Socket 服務。

下面是 Events.php 中的部分程式碼。

public static function onConnect($client_id)
{
    Gateway::sendToClient($client_id, json_encode(array(
        'type'      => 'init',
        'client_id' => $client_id
    )));
}

public static function onMessage($client_id, $message)
{

}

public static function onClose($client_id)
{
    // 房間廣播有連線關閉的訊號
    $room_id = $_SESSION['room_id'];
    $uname   = $_SESSION['uname'];

    if (Gateway::getClientCountByGroup($room_id)) {
        Gateway::sendToGroup($room_id, json_encode(array(
            'type'      => 'close',
            'uname'     => $uname
        )));
    }   
}

然後把所有的 Worker 程序執行起來,開始建立內部通訊並監聽客戶端連線。使用 php start.php start -d 將以守護程序的形式執行所有 Worker程序。

下面在聊天室頁面建立與 GatewayWorker 的 WebSocket 連線

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

注意,websocket 協議來自於 HTML5,某些瀏覽器可能不支援,可以用 

然後在 ws.onmessage 回撥中接收來自 GatewayWorker 發來的 client_id,並用 AJAX 傳送 POST 到 Laravel 框架,進行 uid 與 client_id 的繫結。

前端 Websocket 接收 client_id,併發送 POST 去繫結 uid:

ws.onmessage = function(e) {
    var data = JSON.parse(e.data),
          type = data.type || '';

    switch (type) {
        ...
        case 'init':
            $.post(global_url_bind, {
                client_id: data.client_id,
                _token: global_csrf_token
            }, function(data) {}, 'json');
            break;
        ...
    }
}

後端 Laravel 需要呼叫 GatewayClient API 通知 GatewayWorker 繫結 client_id,首先在 Laravel 專案(聊天室)根目錄下執行 Composer 命令來安裝:

composer require workerman/gatewayclient

然後在需要呼叫 GatewayClient 介面的檔案裡,引用名稱空間:

// GatewayClient 3.0.0版本以後加了名稱空間
use GatewayClient\Gateway;

並設定  Gateway::$registerAddress 屬性,告知 GatewayClient 與哪個 GatewayWorker (叢集)通訊。方便起見,我把它放在了 Laravel 控制器的 __construct() 方法裡:

public function __construct()
{
    Gateway::$registerAddress = '127.0.0.1:1238';
}

這裡再囉嗦一句,這個屬性的設定值必須與前面啟動的 Gateway 程序和 BusinessWorker 程序的 registerAddress 屬性值一致,其中的 1238 埠是由 Register 服務程序監聽的,用於 Gateway 程序和 BusinessWorker 程序內部通訊。

然後,後端 Laravel 繫結 client_id 的程式碼片段如下:

// 繫結uid和client_id、加入房間
Gateway::bindUid($client_id, $uid);  // uid 與 room_id 已經從 Laravel session裡獲取
Gateway::joinGroup($client_id, $room_id);

// 記錄會話
session(['client_id' => $client_id]); // Laravel 負責
Gateway::setSession($client_id, [     // GatewayWorker 負責
    'uid'     => $uid,
    'uname'   => $uname,
    'avatar'  => $avatar,
    'bubble'  => $bubble,
    'room_id' => $room_id
]);

後續的所有房間訊息都直接 get/post 到 Laravel 裡統一處理。下面是聊天部分的部分原始碼:

前端 AJAX:

$.post(global_url_say, {
    type: 'all', // 公聊
    content: content,
    _token: global_csrf_token
}, function(data) {}, 'json');

$.post(global_url_say, {
    type: 'to', // 私聊
    to_uid: $('.to-whom').children('span').attr('id'),
    to_uname: $('.to-whom').children('span').text(),
    content: content,
    _token: global_csrf_token
}, function(data) {}, 'json');

後端 Laravel: 

$type      = $request->input('type') ?: '';
$content   = htmlspecialchars($request->input('content'));
$uid       = session('uid');
$uname     = session('uname');
$avatar    = session('avatar');
$bubble    = session('bubble');
$room_id   = session('room_id');

switch ($type) {
    case 'all': // 公聊
        Gateway::sendToGroup($room_id, json_encode([
            'type'      => 'all',
            'uid'       => $uid,
            'uname'     => $uname,
            'avatar'    => $avatar,
            'bubble'    => $bubble,
            'content'   => preg_replace('/^\s*@me/i', '', $content)
        ]));
        break;
    case 'to':  // 私聊
        $to_uid = $request->input('to_uid');
        Gateway::sendToUid($to_uid, json_encode([
            'type'      => 'to',
            'uid'       => $uid,
            'uname'     => $uname,
            'avatar'    => $avatar,
            'bubble'    => $bubble,
            'content'   => '<a href="javascript:;" style="color: inherit;">@me</a> '.$content
        ]));
        $to_uname = $request->input('to_uname');
        Gateway::sendToUid($uid, json_encode([
            'type'      => 'to',
            'uid'       => $uid,
            'uname'     => $uname,
            'avatar'    => $avatar,
            'bubble'    => $bubble,
            'content'   => '<a href="javascript:;" style="color: inherit;">@'.$to_uname.'</a> '.$content
        ]));
        break;
    ...
}

當有使用者退出房間時,前端 Websocket 會收到來自 Events.php 中 onClose 事件的連線關閉訊號 "close"。這時需要通知房間內其他使用者,並請求 Laravel 拿最新的使用者列表。

 ws.onmessage = function(e) {
    var data = JSON.parse(e.data),
        type = data.type || '';

    switch (type) {
        ...
        case 'close':
            // 通知有人退出
            system_notify('@' + data.uname + ' leaved out.');

            // 請求新的使用者列表
            $.post(global_url_flush, {
                room_id: global_room_id,
                _token: global_csrf_token
            }, function(data) { });
            break;
        ...
    }
}

後端:

$room_id = $request->input('room_id');
$sessions = Gateway::getClientSessionsByGroup($room_id);
$users_list = [];
foreach ($sessions as $client_id => $item) {
    $users_list[$item['uid']] = $item['uname'];
}
$new_message = ['type' => 'flush'];
$new_message['users_list'] = $users_list;
Gateway::sendToGroup($room_id, json_encode($new_message));

然後前端收到 flush 訊息,重新整理使用者列表:

ws.onmessage = function(e) {
    var data = JSON.parse(e.data),
        type = data.type || '';
    
    switch (type) {
        ...
        case 'flush':
            flush_users_list(data.users_list);
            break;
        ...
    }
}

基本的結合邏輯就是這樣了。如果對這個聊天室的原始碼有興趣,可以在下面找到它的地址。謝謝閱讀。

2018-01-26 13:12:19 更新:

聊天室專案已經支援 Windows。

相關連結

chat-here 聊天室原始碼:https://github.com/mingcw/chat-here(這兩天時間有點緊,等 25 號晚上我會新增它的 windows 版本,目前只支援 Linux。)

相關推薦

聊天下篇GatewayWorker Laravel整合

思路 上一篇大概梳理了一下 GatewayWorker 的基礎知識。這篇就來準備整合 GatewayWorker 到 Laravel。 GatewayWorker 是基於 Socket 監聽的伺服器框架,而 Laravel 是基於 HTTP 請求/響應模型的 Web 框架。所以一定要明白,兩者的部署是

用CocosCreator和Pomelo編寫多人在線實時聊天----基礎知識和環境安裝

shu 以及 pan 信息 ast pre alt web 技術 客戶端:Cocos Creator 1.6.2服務器端:Pomelo 2.2.5源碼地址:https://github.com/foupwang/CocosCreatorChatForPomelo.git 本

實現簡易聊天

ima log body .com 麻煩 導入 定義 右鍵 正常 預備工作: (1)讀取文件的時候可能會遇到多個文件一起傳,可以用線程池。 (2)發送不同類型的請求時,如發送的是聊天信息,發送的是文件,發送的是好友上線請求等,但對於接受者來說都是字節流無法分別,這就需要我們

網路程式設計:聊天2

第五步:既然是聊天室,那麼僅僅只能一個使用者自己和自己聊天,顯然該該程式是有瑕疵的。那麼我們就需要支援多使用者同時線上聊天。這一步中,我們就需要用到多執行緒的概念。為什麼要用到多執行緒?執行緒可以通俗的理解為每有一個新運動員便多建造一條跑道,以便所有運動員可以經歷同樣的從頭到尾的全部過程。那如果放到

網路程式設計:聊天1

概述:通過網路程式設計來實現聊天室功能 第一步:建立服務端與客戶端並建立連線         服務端:         import java.io.IOException;     &n

Netty聊天2:從0開始實戰100w級流量應用

目錄 客戶端 Client 登入和響應處理 寫在前面 客戶端的會話管理 客戶端的邏輯構成 連線伺服器與Session 的建立 Session和 channel 相互繫結 AttributeMap介面的使用 客戶端登入請求 處理登入成

#java 聊天—— 給聊天增加選單和私聊功能

#java    聊天室(二)——  給聊天室增加選單和私聊功能 在上一篇部落格裡,我們實現了用java寫了一個telnet聊天伺服器,實現了群聊功能。今天我們就來給這個聊天室新增選單,並且實現私聊功能。  1.實現目標   在使用者登入後顯示選單: 當用

實現一個簡單的語音聊天原始碼

public partial class SpeakerPanel : UserControl ,IDisposable { private ChatUnit chatUnit; public SpeakerPanel()

實現一個簡單的視訊聊天原始碼

       在 《》一文釋出後,很多朋友建議我也實現一個視訊聊天室給他們參考一下,其實,視訊聊天室與語音聊天室的原理是差不多的,由於加入了攝像頭、視訊的處理,邏輯會繁雜一些,本文就實現一個簡單的多人視訊聊天系統,讓多個人可以進入同一個房間進行語音視訊溝通。先看看3個人進行視訊聊天的執行效果截圖:     

MFC——區域網聊天改進

    之前自己用MFC做了個簡易的聊天室,但是功能不多,畫面佈局什麼的也感覺不是太好,而且還存在不少BUG,所以最近又重新拾起過去的程式碼,做了不少的改動並修復了所有的錯誤,修改後的專案的通訊原理還是

網路程式設計之即時通訊程式(聊天)------通訊流程簡介及通訊協議定製

      在開始講之前,我想先跟大家描述一下,這個所謂的通訊程式具體是一個什麼樣的東西。該通訊程式類似一個弱版本的qq,登入時需要進行註冊,登入成功後,可以實現即時的通訊,群聊,私聊,同時還可傳檔案。先上個圖 服務端:                           

linux下自創網路程式設計聊天2

總體設計 本聊天室系統採用了c/s形式。伺服器主要是處理客戶輸入資訊。首先要儲存客戶的個人資料,相當於註冊。再有,在客戶的聊天資訊時,也要記錄下客戶的聊天記錄,已備檢視聊天記錄所用。當然,伺服器還有自己的動態資料處理。客戶狀態分為連結客戶和非連線客戶,我採用結構體儲存連結客

日常Exception:springmybatis整合時報錯

問題:最近在整合SSM框架,搭建一半之後(即專案建立完畢,spring與mybatis整合完畢),開始測試spring與mybatis是否整合成功。貼出部分程式碼:/** * 配置spring和junit整合,junit啟動時載入springIOC容器 spring-tes

FineReport:FineReportWeb整合

一、Web專案整合1、將fineReport安裝目錄下的jar包全部拷貝到web工程下的lib中(資料庫jar只需一個)。2、在web工程WEB-INF下新建兩個目錄reportlets(存在報表模板檔案)和resources(存放資料連線資訊)。3、在web.xml 配置報

6、ActiveMQ入門教程SpringActiveMQ整合

在這一篇部落格分享一下消費者,使用監聽的實現方式。 1. pom.xml 2. 生產者 package org.ygy.mq.lesson04;   import javax.jms.JMSException;   import javax.jms.Mes

聊天上篇GatewayWorker 基礎

前言 本文的目的是基於 GatewayWorker 官方手冊,梳理一次 GatewayWorker,並在實踐中與 MVC 框架整合的思路(附最終的專案原始碼)。如果你已經理解了整合這一塊兒的知識,那麼就可以關掉這個網頁了。時間蠻寶貴的~ 這篇是上篇,梳理 GatewayWorker 基礎,下篇是 Gate

大數據江湖之即席查詢分析下篇--手把手教你搭建即席查詢分析Demo

dmi 安裝centos 用戶 author sla repo 相關 中文 plugin 上篇小弟分享了幾個“即席查詢與分析”的典型案例,引起了不少共鳴,好多小夥伴迫不及待地追問我們:說好的“手把手教你搭建即席查詢與分析Demo”啥時候能出?說到就得做到,差啥不能差

Qt的網絡通信以一對一聊天為例

lis sci idg ESS host 文字 btn stdstring nec 一、以一對一(服務器,客戶端)為例   1、服務器:      1、在目錄文件 .pro文件中   QT += core gui network 添加network      

仿班級聊天DOM原型法並且用localStorage儲存訊息記錄

第一部分:CSS程式碼 * {                 margin: 0px;