聊天室(下篇)GatewayWorker 與 Laravel 的整合
思路
上一篇大概梳理了一下 GatewayWorker 的基礎知識。這篇就來準備整合 GatewayWorker 到 Laravel。
GatewayWorker 是基於 Socket 監聽的伺服器框架,而 Laravel 是基於 HTTP 請求/響應模型的 Web 框架。所以一定要明白,兩者的部署是獨立分開、互不干擾的。
因此在物理上它們的整合方式就見仁見智了。而官方, walkor 大神(GatewayWorker 框架作者)在手冊裡也給出了與 MVC 框架的結合方式,如下圖所示:
客戶端瀏覽器建立與 GatewayWorker 的 WebSocket 連線,所有的業務邏輯由客戶端通過 HTTP 協議 GET/POST 到 Web 框架,由 Web 框架統一處理。僅當客戶端瀏覽器需要主動推送資料時, Web 框架將呼叫 GatewayWorker 提供的 API(
步驟
大致的思路有了,具體的實現步驟配合聊天室部分程式碼,如下所示。
首先要定製 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(五):spring與mybatis整合時報錯
問題:最近在整合SSM框架,搭建一半之後(即專案建立完畢,spring與mybatis整合完畢),開始測試spring與mybatis是否整合成功。貼出部分程式碼:/** * 配置spring和junit整合,junit啟動時載入springIOC容器 spring-tes
FineReport(一):FineReport與Web整合
一、Web專案整合1、將fineReport安裝目錄下的jar包全部拷貝到web工程下的lib中(資料庫jar只需一個)。2、在web工程WEB-INF下新建兩個目錄reportlets(存在報表模板檔案)和resources(存放資料連線資訊)。3、在web.xml 配置報
6、ActiveMQ入門教程(六)Spring與ActiveMQ整合
在這一篇部落格分享一下消費者,使用監聽的實現方式。 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;