PHP系列 | PHP跨平台实时通讯框架 Socket.IO 的应用

PHPSocket.IO 是什么?

  1. PHPSocket.IO是PHP版本的Socket.IO服务端实现,基于workerman开发,用于替换node.js版本Socket.IO服务端。

  2. PHPSocket.IO底层采用websocket协议通讯,如果客户端不支持websocket协议, 则会自动采用http长轮询的方式通讯。

  3. PHPSocket.IO实现的Polling通信机制包括Adobe Flash Socket、AJAX长轮询、JSONP轮询等。具体采用哪种机制通讯对于开发者完全透明, 开发者使用的是统一的接口。

设计的目标

利用PHP构建能够在不同浏览器和移动设备上良好运行的实时应用,如实时分析系统、在线聊天室、在线客服系统、评论系统、WebIM等。

PHPSocket.IO与workerman的区别是:

PHPSocket.IO基于workerman开发,workerman有的特性PHPSocket.IO都支持。PHPSocket.IO最大的优势是对各种浏览器的兼容性更好。

文档&&安装

文档仓库:https://github.com/walkor/phpsocket.io 使用composer安装

  1. composer require workerman/phpsocket.io

应用案例

Nginx 主机配置

    1. server {
 
    1. listen 443 ssl http2;
 
    1. server_name www.tinywan.com;
 
    1. ssl_certificate letsencrypt/iot.tinywan.com/full_chain.pem;
 
    1. ssl_certificate_key letsencrypt/iot.tinywan.com/private.key;
 
    1. set $root_path /var/www/iot.tinywan.com/public;
 
    1. root $root_path;
 
    1. more_set_headers "X-Frame-Options: SAMEORIGIN";
 
    1.  
 
    1. location / {
 
    1. if (!-e $request_filename) {
 
    1. rewrite ^(.*)$ /index.php?s=/$1 last;
 
    1. break;
 
    1. }
 
    1. }
  
    1.  
 
    1. # SocketIO 配置
 
    1. location /socket.io {
 
    1. proxy_pass http://127.0.0.1:2120;
 
    1. proxy_http_version 1.1;
 
    1. proxy_set_header Upgrade $http_upgrade;
 
    1. proxy_set_header Connection "Upgrade";
 
    1. proxy_set_header X-Real-IP $remote_addr;
 
    1. }
 
    1. }
 

服务端完整代码

    1. <?php
 
    1. namespace apphttpcontroller;
 
    1.  
 
    1. use PHPSocketIOSocketIO;
 
    1. use thinkfacadeLog;
 
    1. use WorkermanWorker;
 
    1.  
 
    1. class Server
 
    1. {
 
    1. /**
 
    1. * @author: Tinywan(Shaobo Wan)
 
    1. * @time: 2019/4/22 19:51
 
    1. */
 
    1. public function server()
 
    1. {
 
    1. // 全局数组保存uid在线数据
 
    1. $uidConnectionMap = array();
 
    1. // 记录最后一次广播的在线用户数
 
    1. $last_online_count = 0;
 
    1. // 记录最后一次广播的在线页面数
 
    1. $last_online_page_count = 0;
 
    1. // PHPSocketIO服务
 
    1. $sender_io = new SocketIO(2120);
 
    1. // 客户端发起连接事件时,设置连接socket的各种事件回调
 
    1. $sender_io->on('connection', function ($socket) {
 
    1. Log::info('客户端发起连接事件 ');
 
    1. // 当客户端断开连接是触发(一般是关闭网页或者跳转刷新导致)
 
    1. $socket->on('login', function ($uid) use ($socket) {
 
    1. Log::info('客户端登录uid ' . $uid);
 
    1. global $uidConnectionMap, $last_online_count, $last_online_page_count;
 
    1. // 已经登录过了
 
    1. if (isset($socket->uid)) {
 
    1. return;
 
    1. }
 
    1. // 更新对应uid的在线数据
 
    1. $uid = (string)$uid;
 
    1. if (!isset($uidConnectionMap[$uid])) {
 
    1. $uidConnectionMap[$uid] = 0;
 
    1. }
 
    1. // 这个uid有++$uidConnectionMap[$uid]个socket连接
 
    1. ++$uidConnectionMap[$uid];
 
    1. // 将这个连接加入到uid分组,方便针对uid推送数据
 
    1. $socket->join($uid);
 
    1. $socket->uid = $uid;
 
    1. });
 
    1. // 当客户端断开连接是触发(一般是关闭网页或者跳转刷新导致)
 
    1. $socket->on('disconnect', function () use ($socket) {
 
    1. Log::info('客户端断开 ' . json_encode($socket));
 
    1. if (!isset($socket->uid)) {
 
    1. return;
 
    1. }
 
    1. global $uidConnectionMap, $sender_io;
 
    1. // 将uid的在线socket数减一
 
    1. if (--$uidConnectionMap[$socket->uid] <= 0) {
 
    1. unset($uidConnectionMap[$socket->uid]);
 
    1. }
 
    1. });
 
    1. });
 
    1. // 当$sender_io启动后监听一个http端口,通过这个端口可以给任意uid或者所有uid推送数据
 
    1. $sender_io->on('workerStart', function () use ($sender_io) {
 
    1. // 监听一个http端口
 
    1. $inner_http_worker = new Worker('http://0.0.0.0:2121');//这里IP不用改变,用的内网通讯,端口不能与socket端口想通
 
    1. $inner_http_worker->onMessage = function ($http_connection, $data) use ($sender_io) {
 
    1. $postData = $data['post'];
 
    1. $to = $postData['to'] ?? '';
 
    1. Log::info('发送到用户to ' . json_encode($to));
 
    1. $content = htmlspecialchars($postData['content']);
 
    1. // 有指定uid则向uid所在socket组发送数据
 
    1. if ($to) {
 
    1. $sender_io->to($to)->emit('new_msg', $content);
 
    1. } else {
 
    1. // 否则向所有uid推送数据
 
    1. $sender_io->emit('new_msg', $content);
 
    1. }
 
    1. // http接口返回,如果用户离线socket返回fail
 
    1. if ($to && !isset($uidConnectionMap[$to])) {
 
    1. return $http_connection->send('offline');
 
    1. } else {
 
    1. return $http_connection->send('ok');
 
    1. }
 
    1. };
 
    1. // 执行监听
 
    1. $inner_http_worker->listen();
 
    1. });
 
    1. Worker::runAll();
 
    1. }
 
    1. }
 

1、启动服务端端: php web_msg.php start-d

    1. $ php web_msg.php start -d
 
    1. Workerman[web_msg.php] start in DAEMON mode
 
    1. ------------------------------------------- WORKERMAN -------------------------------------------
 
    1. Workerman version:3.5.20 PHP version:7.2.9
 
    1. -------------------------------------------- WORKERS --------------------------------------------
 
    1. proto user worker listen processes status
 
    1. tcp www PHPSocketIO socketIO://0.0.0.0:2120 1 [OK]
 

2、发送消息

    1. function send_web_msg($to_uid = 1, $content)
 
    1. {
 
    1. if (empty($content)) {
 
    1. return ["error_code" => 404, "reason" => '缺少参数'];
 
    1. }
 
    1. $push_api_url = "http://127.0.0.1:2121/";
 
    1. $post_data = [
 
    1. "type" => "publish",
 
    1. "content" => $content,
 
    1. "to" => $to_uid
 
    1. ];
 
    1. $ch = curl_init();
 
    1. curl_setopt($ch, CURLOPT_URL, $push_api_url);
 
    1. curl_setopt($ch, CURLOPT_POST, 1);
 
    1. curl_setopt($ch, CURLOPT_HEADER, 0);
 
    1. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 
    1. curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
 
    1. curl_setopt($ch, CURLOPT_HTTPHEADER, array("Expect:"));
 
    1. $return = curl_exec($ch);
 
    1. curl_close($ch);
 
    1. return $return;
 
    1. }
 
    1.  
 
    1. send_web_msg(1, "哈喽,Tinywan先生");
 

客户端完整代码

html 代码

  1. <div><p id="notice-content" style="float: left">系统公告</p></div>

JS 代码

    1. <script src='https://cdn.bootcss.com/socket.io/2.0.3/socket.io.js'></script>
 
    1. <script src="https://cdn.bootcss.com/notify.js/3.0.0/notify.js"></script>
 
    1. <script>
 
    1. $(document).ready(function () {
 
    1. $uid = "1";
 
    1. var socket = io("https://www.tinywan.com", { path: '/socket.io' });
 
    1. socket.on('connect', function () {
 
    1. console.log('连接成功');
 
    1. socket.emit('login', $uid);
 
    1. });
 
    1. socket.on('new_msg', function (msg) {
 
    1. console.log('系统消息:' + msg);
 
    1. $('#notice-content').html('系统提示:' + msg);
 
    1. $('.notification.sticky').notify();
 
    1. });
 
    1. });
 
    1. </script>
 

效果图

登入/注册
卧槽~你还有脸回来
没有账号? 忘记密码?