PHPSocket.IO,PHP跨平台实时通讯框架
PHPSocket.IO是PHP版本的Socket.IO服务端实现,基于workerman开发,用于替换node.js版本Socket.IO服务端。PHPSocket.IO底层采用websocket协议通讯,如果客户端不支持websocket协议, 则会自动采用http长轮询的方式通讯。

环境

  • Ubuntu 18
  • Laravel 5.8
  • PHPSocket.IO 1.1

安装依赖

composer require workerman/phpsocket.iocomposer require guzzlehttp/guzzle

启动程序整合到artisan命令中

创建文件命令php artisan make:command MsgPush

app/Console/Commands/MsgPush.php

<?phpnamespace 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;        }    }}

启动PHPSocket.Io服务

#守护进程模式启动php artisan msg-push start -d#调式模式启动php artisan msg-push start

web页面

resources/views/socketio.blade.php

<!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>

web页面路由

routes/web.php

Route::get('/socketio', function () {    return view('socketio');});

laravel内以触发事件方式推送消息

app/Providers/EventServiceProvider.php

//定义事件//App/Providers/EventServiceProviderpublic 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或域名

tinker方式测试推送

#进入tinkerphp artisan tinker#推送给全体event('send-msg',(object)['content'=>'hello'])#推送给个体,`to`改成你的实际值event('send-msg',(object)['content'=>'hello','to'=>1556645595484])

通过以上操作即可在php服务端向web端推送消息啦,解锁新功能是不是有点小兴奋呢?
感谢推动着时代进步的巨人们,是你们让我等看到了更多的可能!

参考

workerman手册
PHPSocket.IO跨平台实时通讯框架简介