項目地址 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/