PHPSocket.IO,PHP跨平臺實時通信框架
PHPSocket.IO是PHP版本的Socket.IO服務端實現,基於workerman開發,用於替換node.js版本Socket.IO服務端。PHPSocket.IO底層採用websocket協議通信,若是客戶端不支持websocket協議, 則會自動採用http長輪詢的方式通信。
composer require workerman/phpsocket.io composer require guzzlehttp/guzzle
建立文件命令php artisan make:command MsgPush
php
app/Console/Commands/MsgPush.php
css
<?php namespace App\Console\Commands; use Illuminate\Console\Command; use Workerman\Worker; use Workerman\Lib\Timer; use PHPSocketIO\SocketIO; class MsgPush extends Command { protected $signature = 'msg-push {action=start : start | restart | reload(平滑重啓) | stop | status | connetions} {--d : deamon or debug}'; protected $description = 'web消息推送服務'; // 全局數組保存uid在線數據 private static $uidConnectionCounter = []; // 廣播的在線用戶數,一個uid表明一個用戶 private static $onlineCount = 0; // 廣播的在線頁面數,同一個uid可能開啓多個頁面 private static $onlinePageCount = 0; //PHPSocketIO服務 private static $senderIo = null; public function __construct() { parent::__construct(); } /** * 根據腳本參數開啓PHPSocketIO服務 * PHPSocketIO服務的端口是`2120` * 傳遞數據的端口是`2121` */ public function handle() { global $argv; //啓動php腳本所需的命令行參數 $argv[0] = 'MsgPush'; $argv[1] = $this->argument('action'); // start | restart | reload(平滑重啓) | stop | status | connetions $argv[2] = $this->option('d') ? '-d' : ''; // 守護進程模式或調試模式啓動 // PHPSocketIO服務 self::$senderIo = new SocketIO(2120); // 客戶端發起鏈接事件時,設置鏈接socket的各類事件回調 self::$senderIo->on('connection', function ($socket) { // 當客戶端發來登陸事件時觸發,$uid目前由頁面傳值決定,固然也能夠根據業務須要由服務端來決定 $socket->on('login', function ($uid) use ($socket) { // 已經登陸過了 if (isset($socket->uid)) return; // 更新對應uid的在線數據 $uid = (string)$uid; // 這個uid有self::$uidConnectionCounter[$uid]個socket鏈接 self::$uidConnectionCounter[$uid] = isset(self::$uidConnectionCounter[$uid]) ? self::$uidConnectionCounter[$uid] + 1 : 1; // 將這個鏈接加入到uid分組,方便針對uid推送數據 $socket->join($uid); $socket->uid = $uid; // 更新這個socket對應頁面的在線數據 self::emitOnlineCount(); }); // 當客戶端斷開鏈接是觸發(通常是關閉網頁或者跳轉刷新致使) $socket->on('disconnect', function () use ($socket) { if (!isset($socket->uid)) { return; } // 將uid的在線socket數減一 if (--self::$uidConnectionCounter[$socket->uid] <= 0) { unset(self::$uidConnectionCounter[$socket->uid]); } }); }); // 當self::$senderIo啓動後監聽一個http端口,經過這個端口能夠給任意uid或者全部uid推送數據 self::$senderIo->on('workerStart', function () { // 監聽一個http端口 $innerHttpWorker = new Worker('http://0.0.0.0:2121'); // 當http客戶端發來數據時觸發 $innerHttpWorker->onMessage = function ($httpConnection, $data) { $type = $_REQUEST['type'] ?? ''; $content = htmlspecialchars($_REQUEST['content'] ?? ''); $to = (string)($_REQUEST['to'] ?? ''); // 推送數據的url格式 type=publish&to=uid&content=xxxx switch ($type) { case 'publish': // 有指定uid則向uid所在socket組發送數據 if ($to) { self::$senderIo->to($to)->emit('new_msg', $content); } else { // 不然向全部uid推送數據 self::$senderIo->emit('new_msg', $content); } // http接口返回,若是用戶離線socket返回fail if ($to && !isset(self::$uidConnectionCounter[$to])) { return $httpConnection->send('offline'); } else { return $httpConnection->send('ok'); } } return $httpConnection->send('fail'); }; // 執行監聽 $innerHttpWorker->listen(); // 一個定時器,定時向全部uid推送當前uid在線數及在線頁面數 Timer::add(1, [self::class, 'emitOnlineCount']); }); // Worker::$daemonize = true; Worker::runAll(); } /** * 將在線數變化推送給全部登陸端 * 須是public方法,可供其它類調用 */ public static function emitOnlineCount() { $newOnlineCount = count(self::$uidConnectionCounter); $newOnlinePageCount = array_sum(self::$uidConnectionCounter); // 只有在客戶端在線數變化了才廣播,減小沒必要要的客戶端通信 if ($newOnlineCount != self::$onlineCount || $newOnlinePageCount != self::$onlinePageCount) { // var_dump('emitOnlineCount: ', self::$uidConnectionCounter); //將在線數變化推送給全部登陸端 self::$senderIo->emit( 'update_online_count', [ 'onlineCount' => $newOnlineCount, 'onlinePageCount' => $newOnlinePageCount ] ); self::$onlineCount = $newOnlineCount; self::$onlinePageCount = $newOnlinePageCount; } } }
#以守護進程模式啓動 php artisan msg-push start -d #以調式模式啓動 php artisan msg-push start #終止 php artisan msg-push stop #平滑重啓 php artisan msg-push reload
resources/views/socketio.blade.php
html
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title>laravel整合phpSocketIo</title> </head> <body> <h1>laravel整合phpSocketIo</h1> <h2>實現laravel服務端推送消息到web端</h2> <h5>效果查看console</h5> <script src='https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js'></script> <script> document.addEventListener('DOMContentLoaded', () => { const uid = Date.now(), //這個識別id能夠換成項目相應業務的id,同一個id能夠多端登陸,能同時收到消息 domain = document.domain, //當前打開頁面的域名或ip sendToOneApi = `http://${domain}:2121/?type=publish&content=msg_content&to=${uid}`, sendToAllApi = `http://${domain}:2121/?type=publish&content=msg_content`, socket = io(`http://${domain}:2120`); // 鏈接socket服務端 console.log('給指定uid登陸端發送消息接口: ', sendToOneApi); //支持get和post方法 console.log('給全部登陸端發送消息接口: ', sendToAllApi); // 鏈接後登陸 socket.on('connect', function () { socket.emit('login', uid); }); // 後端推送來消息時 socket.on('new_msg', function (msg) { console.log('收到消息: ' + msg); }); // 後端推送來在線數據時 socket.on('update_online_count', function (online_stat) { console.log('即時在線數據: ', online_stat); }); }); </script> </body> </html>
routes/web.php
node
Route::get('/socketio', function () { return view('socketio'); });
app/Providers/EventServiceProvider.php
laravel
//定義事件 //App/Providers/EventServiceProvider public function boot() { parent::boot(); //推送消息到web端,這個閉包只能傳入一個參數 Event::listen('send-msg', function (object $data) { // dump($data); $response = (new \GuzzleHttp\Client())->post('http://127.0.0.1:2121', [ 'form_params' => [ 'content' => $data->content, 'to' => $data->to ?? '', 'type' => $data->type ?? 'publish', ], ]); return (string)$response->getBody(); }); }
地址欄輸入http://${domain}:2121/?type=publish&content=Are_you_ok
推送給全體成員,${domain}
是你實際的ip或域名web
#進入tinker php artisan tinker #推送給全體 event('send-msg',(object)['content'=>'hello']) #推送給個體,`to`改爲你的實際值 event('send-msg',(object)['content'=>'hello','to'=>1556645595484])
經過以上操做便可在php服務端向web端推送消息啦,解鎖新功能是否是有點小興奮呢?shell
workerman手冊
PHPSocket.IO跨平臺實時通信框架簡介後端
感謝推進着時代進步的巨人們,是大家讓我等看到了更多的可能!數組