一、基礎
1. workerman
workerman是國人開發的良心高性能的PHP socket 服務器框架,在全球最大同性交友平臺gayHub的star都4K多,可以想象是多么的牛X。
可以單獨部署,也可以整合進MVC的框架(TP,laravel等),可以說非常實用,并發效果也好。
官網地址:
http://www.workerman.net/workerman
gayhub地址:
https://github.com/walkor/workerman/
2. gateway-worker
gateway-worker(后面直接稱gateway)是基于 workerman開發的TCP長連接框架,用于快速開發TCP長連接應用。
在線聊天一般都是實用長連接保持通信,使用 workerman雖然能夠做到同樣的效果,但是gateway更加的方便快捷。
(輪詢構建的聊天室已經OUT了,實在是太…)
gayhub地址:
https://github.com/walkor/GatewayWorker
3. gatewayClient
gateClient是用來輔助 workerman或者是gateway進行用戶分組以及向用戶發送信息的組件,同時,能夠快速便捷的將原有系統的uid和clientid綁定起來。
gayhub地址:
https://github.com/walkor/GatewayClient
二、理論:
1. 與MVC系統整合的原則:
·現有mvc框架項目與GatewayWorker獨立部署互不干擾;
·所有的業務邏輯都由網站頁面post/get到mvc框架中完成;
·GatewayWorker不接受客戶端發來的數據,即GatewayWorker不處理任何業務邏輯,GatewayWorker僅僅當做一個單向的推送通道;
·僅當mvc框架需要向瀏覽器主動推送數據時才在mvc框架中調用Gateway的API(GatewayClient)完成推送。
2. 實現步驟:
(1)網站頁面建立與GatewayWorker的websocket連接;
(2)GatewayWorker發現有頁面發起連接時,將對應連接的client_id發給網站頁面;
(3)網站頁面收到client_id后觸發一個ajax請求(假設是bind.php)將client_id發到mvc后端;
(4)mvc后端bind.php收到client_id后利用GatewayClient調用Gateway::bindUid($client_id, $uid)將client_id與當前uid(用戶id或者客戶端唯一標識)綁定。如果有群組、群發功能,也可以利用Gateway::joinGroup($client_id, $group_id)將client_id加入到對應分組;
(5)頁面發起的所有請求都直接post/get到mvc框架統一處理,包括發送消息;
(6)mvc框架處理業務過程中需要向某個uid或者某個群組發送數據時,直接調用GatewayClient的接口Gateway::sendToUid Gateway::sendToGroup 等發送即可。
三、實現—配置和開啟Gateway:
1.下載和使用gateway
可以單獨使用,也可以放在框架的public目錄下。
2.編輯start.php
·start.php是需要使用php命令行運行的。
·注意require_once的路徑
ini_set('display_errors',?'on'); use?WorkermanWorker; if(strpos(strtolower(PHP_OS),?'win')?===?0) { ????exit("start.php?not?support?windows,?please?use?start_for_win.batn"); } //?檢查擴展 if(!extension_loaded('pcntl')) { ????exit("Please?install?pcntl?extension.See?http://doc3.workerman.net/appendices/install-extension.htmln"); } if(!extension_loaded('posix')) { ????exit("Please?install?posix?extension.See?http://doc3.workerman.net/appendices/install-extension.htmln"); } //?標記是全局啟動 define('GLOBAL_START',?1); //?注意這里的路徑 require_once?'../vendor/autoload.php'; //?加載所有Applications/*/start.php,以便啟動所有服務 foreach(glob(__DIR__.'/Applications/*/start*.php')?as?$start_file) { ????require_once?$start_file; } //?運行所有服務 Worker::runAll();
3. start_gateway.php
·在ApplicationsYourAppstart_gateway.php中可以編輯
//?部分文件內容 //將$gateway改成websocket協議,demo中是text協議 $gateway?=?new?Gateway("websocket://0.0.0.0:8282");
4.start_register.php
·需要注意start_register.php 中$register必須是text協議,同時需要注意端口
//?register?服務必須是text協議 $register?=?new?Register('text://192.168.124.125:1238');
5. 配置好后,開啟start.php
$?php?start.php?start
四、實現-服務端開發
上面提到了,用戶只有在觸發連接的時候才經過gateway的onConnect($client_id),而所有的業務操作都應該在web系統中實現。
因此我創建了一個GatewatServer.php的controller,負責處理這些業務
<?php /** * Author: root * Date : 17-3-27 * time : 上午12:32 */ namespace appindexcontroller; use GatewayClientGateway; use thinkCache; use thinkController; use thinkRequest; use thinkSession; class GatewayServer extends Controller { public function _initialize(){ } public function bind(Request $request) { // 用戶連接websocket之后,綁定uid和clientid,同時進行分組,根據接收到的roomid進行分組操作 $userGuid=Session::get('loginuser'); $roomId=intval(trimAll($request->post('room'))); ????????$clientId=trimAll($request->post('client_id')); ????????//?接受到上面的三個參數,進行分組操作 ????????Gateway::$registerAddress?=?'192.168.124.125:1238'; ????????//?client_id與uid綁定 ????????//?Gateway::bindUid($clientId,?$userGuid); ????????//?加入某個群組(可調用多次加入多個群組)?將clientid加入roomid分組中 ????????Gateway::joinGroup($clientId,?$roomId); ????????//?返回ajax?json信息 ????????$dataArr=[ ????????????'code'=>$userGuid, ????????????'status'=>true, ????????????'message'=>'Group?Success' ????????]; ????????return?json()->data($dataArr); ????} ????//?接受用戶的信息?并且發送 ????public?function?send(Request?$request){ ????????Gateway::$registerAddress?=?'192.168.124.125:1238'; ????????//?獲得數據 ????????$userGuid=Session::get('loginuser'); ????????$roomId=intval(trimAll($request->post('room'))); ????????$message=trim($request->post('message')); ????????//?獲得用戶的稱呼 ????????$userInfo=Cache::get($userGuid); ????????//?將用戶的昵稱以及用戶的message進行拼接 ????????$nickname=$userInfo['nickname']; ????????$message=$nickname."?:?".$message; ????????//?發送信息應當發送json數據,同時應該返回發送的用戶的guid,用于客戶端進行判斷使用 ????????$dataArr=json_encode(array( ????????????'message'?=>?$message, ????????????'user'=>$userGuid ????????)); ????????//?向roomId的分組發送數據 ????????Gateway::sendToGroup($roomId,$dataArr); ????} }
五、實現-客戶端連接與發送/接收:
開啟了gateway之后,就可以監聽并且等待瀏覽器接入了。
客戶端這里使用js監聽websocket:
1. 用于處理客戶端連接websocket以及接收消息
//?這個示例和gateway官網的示例是一樣的 ????//?監聽端口 ????ws?=?new?WebSocket("ws://192.168.124.125:8282"); ????//?綁定分組的ajaxURL ????var?ajaxUrl="{:url('/gateway/bind')}"; ????//?發送消息的ajaxURL ????var?ajaxMsgUrl="{:url('/gateway/send')}"; ????//?通過房間號進行分組 ????var?roomId="{$roomInfo.guid}"; ????//?獲取當前登錄用戶的guid,用于標識是自己發送的信息 ????var?loginUser="{$userLoginInfo.guid}"; ????//?獲取當前房間號的主播的uid,用于標識是主播發送的信息 ????var?roomUser="{$roomInfo.uid}"; ????//?服務端主動推送消息時會觸發這里的onmessage ????ws.onmessage?=?function(e){ ????????//?console.log(e.data); ????????//?json數據轉換成js對象 ????????var?data?=?eval("("+e.data+")"); ????????var?type?=?data.type?||?''; ????????switch(type){ ????????????//?Events.php中返回的init類型的消息,將client_id發給后臺進行uid綁定 ????????????case?'init': ????????????????//?利用jquery發起ajax請求,將client_id發給后端進行uid綁定 ????????????????$.post(ajaxUrl,?{client_id:?data.client_id,room:roomId},?function(data){ ????????????????????//?console.log(data); ????????????????},?'json'); ????????????????break; ????????????//?當mvc框架調用GatewayClient發消息時直接alert出來 ????????????default?: ????????????????//?如果登陸用戶的guid和數據發送者的guid一樣,則使用不同的顏色(只能自己看到) ????????????????if(loginUser?==?data.user){ ????????????????????addMsgToHtml(data.message,'#F37B1D'); ????????????????????break; ????????????????//?如果發送者的guid和主播uid一樣,則對所有的顯示都增加一個[主播標識] ????????????????}else?if(data.user==roomUser){ ????????????????????addMsgToHtml("[主播]?"+data.message,'#0e90d2'); ????????????????????break; ????????????????}else{ ????????????????//?其他的就正常發送消息 ????????????????????addMsgToHtml(data.message,'#333'); ????????????????} ????????????????break; ????????} ????};
2. 用于將接收到的消息添加到div中進行顯示
//?向面板中增加新接收到的消息 ????//?其中message是消息,color是顯示的顏色,主要為了區分主播以及自己發送的消息和系統提示 ????function?addMsgToHtml(message,color)?{ ????????if(message.length==0){ ????????????return?false; ????????} ????????//?獲取html,并且增加html ????????var?obj=$("#room-viedo-chat"); ????????var?html=obj.html(); ????????//? ????????html+='<p><font>'+message+'</font></p>'; ????????obj.html(html); ????????//?將滾動條滾動到底部 ????????obj.scrollTop(obj[0].scrollHeight); ????}
3.用于發送消息
//?發送聊天消息 ????function?sendMsg(){ ????????//?去掉onclick屬性,使得3秒之內無法發送信息 ????????$("#sendMsgBox").attr('onclick',''); ????????var?btnObj=$("#sendMsgBtn"); ????????var?tmpNum=3; ????????var?tmpMsg=tmpNum+'?S'; ????????btnObj.text(tmpMsg); ????????var?int?=setInterval(function?()?{ ????????????//?3秒之內不能發送信息,3秒之后,回復onclick屬性以及文字 ????????????if(tmpNum==0){ ????????????????tmpMsg="發送"; ????????????????clearInterval(int); ????????????????btnObj.text("發送"); ????????????????$("#sendMsgBox").attr('onclick','sendMsg()'); ????????????} ????????????btnObj.text(tmpMsg); ????????????tmpNum-=1; ????????????tmpMsg=tmpNum+'?S'; ????????},1000); ????????var?message=$("#chattext").val().trim(); ????????var?obj=$("#room-viedo-chat"); ????????var?html=obj.html(); ????????if(message.length>=140){ ????????????//?獲取html,并且增加html ????????????addMsgToHtml("系統提示:?不能超過140個字符","#8b0000"); ????????????return?false; ????????} ????????if(message.length==0){ ????????????//?獲取html,并且增加html ????????????addMsgToHtml("系統提示:?不能發送空消息","#8b0000"); ????????????return?false; ????????} ????????//?向server端發送ajax請求 ????????$.post(ajaxMsgUrl,{room:roomId,message:message},function?(data)?{ ????????},'json'); ????????return?false; ????}
4.一點兒html代碼
<!--chat box start --> ????<div> ????</div> ????<div> ????????<div> ????????????<textarea></textarea> ????????</div> ????????<div> ????????????<span> ????????????????發送 ????????????</span> ????????</div> ????</div> ????<!--chat box end -->
六、效果:
效果很明顯:
·系統提示是單獨的顏色
·本人發布的,是自己能夠分辨的橙色
·主播發布的是藍色,同時前面有[主播]標識
·看其他人發布的就是普通的顏色
PHP中文網,有大量免費的workerman入門教程,歡迎大家學習!