這次要開發聊天系統 , 需要用到WebSocket 我使用的是workerman+gateway,為了方便后面再用,做個簡單記錄
首先要特別注意的是,端口要開放,如果端口未開放,會出現連接時握手失敗的情況,這里我用的商品是 801 和 802
1、安裝workerman和gateway
composer require topthink/think-worker
composer require workerman/gatewayclient
2、添加server.php文件,后成需要在CLI模式下運行
#!/usr/bin/env php <?php 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','push/Run'); // 加載框架引導文件 require __DIR__ . '/thinkphp/start.php';
3、創建push模塊新建兩個控制器Run.php Events.php
Start.php
<?php namespace app\push\controller; use Workerman\Worker; use GatewayWorker\Register; use GatewayWorker\BusinessWorker; use GatewayWorker\Gateway; class Run extends Worker{ /** * 構造函數 * @access public */ public function __construct(){ //由於是手動添加,因此需要注冊命名空間,方便自動加載,具體代碼路徑以實際情況為准 \think\Loader::addNamespace([ 'Workerman' => VENDOR_PATH . 'Workerman/workerman', 'GatewayWorker' =>VENDOR_PATH . 'Workerman/gateway-worker/src', ]); //初始化各個GatewayWorker //初始化register new Register('text://0.0.0.0:801'); //初始化 bussinessWorker 進程 $worker = new BusinessWorker(); $worker->name = 'WebIMBusinessWorker'; $worker->count = 4; $worker->registerAddress = '127.0.0.1:801'; //設置處理業務的類,此處制定Events的命名空間 $worker->eventHandler = '\app\push\controller\Events'; // 初始化 gateway 進程 $gateway = new Gateway("websocket://0.0.0.0:802"); $gateway->name = 'WebIMGateway'; $gateway->count = 4; $gateway->lanIp = '127.0.0.1'; $gateway->startPort = 2900; $gateway->registerAddress = '127.0.0.1:801'; //運行所有Worker; Worker::runAll(); } }
Events.php
<?php namespace app\push\controller; use GatewayWorker\Lib\Gateway; /** * 主邏輯 * 主要是處理 onConnect onMessage onClose 三個方法 * onConnect 和 onClose 如果不需要可以不用實現並刪除 */ class Events{ /** * 當客戶端發來消息時觸發 * @param int $client_id 連接id * @param mixed $data 具體消息 */ public static function onMessage($client_id, $data){ $message = json_decode($data, true); $message_type = $message['type']; switch($message_type) { case 'init': // uid $uid = $message['id']; // 設置session $_SESSION = [ 'username' => $message['username'], 'avatar' => isset($message['avatar'])?$message['avatar']:'', 'id' => $uid, 'sign' => $message['sign'], 'type'=> $type, ]; // 將當前鏈接與uid綁定 Gateway::bindUid($client_id, $uid); // 通知當前客戶端初始化 $init_message = array( 'message_type' => 'init', 'id' => $uid, ); Gateway::sendToClient($client_id, json_encode($init_message)); return; break; case 'chatMessage': // 聊天消息 $type = $message['data']['to']['type']; $to_id = $message['data']['to']['id']; $uid = $_SESSION['id']; $chat_message = [ 'message_type' => 'chatMessage', 'data' => [ 'username' => $_SESSION['username'], 'avatar' => $_SESSION['avatar'], 'id' => $type === 'friend' ? $uid : $to_id, 'type' => $type, 'content' => htmlspecialchars($message['data']['mine']['content']), 'timestamp'=> time()*1000, ] ]; return Gateway::sendToUid($to_id, json_encode($chat_message)); break; case 'hide': case 'online': $status_message = [ 'message_type' => $message_type, 'id' => $_SESSION['id'], ]; $_SESSION['online'] = $message_type; Gateway::sendToAll(json_encode($status_message)); return; break; case 'ping': return; default: echo "unknown message $data" . PHP_EOL; } } /** * 當客戶端連接時觸發 * 如果業務不需此回調可以刪除onConnect * * @param int $client_id 連接id */ public static function onConnect($client_id) { var_dump($client_id); Gateway::sendToClient($client_id,json_encode(['status'=>"success",'msg'=>"連接成功"])); Gateway::sendToAll("連接成功"); } /** * 當連接斷開時觸發的回調函數 * @param $connection */ public static function onClose($client_id){ $logout_message = [ 'message_type' => 'logout', 'id' => $_SESSION['id'] ]; Gateway::sendToAll(json_encode($logout_message)); } /** * 當客戶端的連接上發生錯誤時觸發 * @param $connection * @param $code * @param $msg */ public static function onError($client_id, $code, $msg) { echo "error $code $msg\n"; } /** * 每個進程啟動 * @param $worker */ public static function onWorkerStart($worker) { } }
然后執行命令開啟進程
php server.php start
前端示例:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> </head> <body> <script> ws = new WebSocket("ws:0.0.0.0:802"); //填你線上服務端地址 // 服務端主動推送消息時會觸發這里的onmessage ws.onopen = function(){ console.info("與服務端連接成功"); ws.send('test msg\n');//相當於發送一個初始化信息 console.info("向服務端發送心跳包字符串"); }; ws.onmessage = function(e){ // json數據轉換成js對象 var data = eval("("+e.data+")"); console.log(data.msg); }; ws.onclose = function(e){ console.log(e); }; </script> </body> </html>