PHP使用workerman框架的websocket, 实现机器扫码检票, 异步刷新小程序页面的票状态.
首先,需要小程序前端页面使用openid作为标志(代码里是$uid)连接websocketServer
然后, 检票接口做完检票逻辑后, 将结果通过websocketClient推送到小程序页面
最后, 小程序根据接收到的推送消息处理页面
参考《看完让你彻底搞懂Websocket原理》(https://www.cnblogs.com/fuqiang88/p/5956363.html)
深入浅出地介绍了websock的原理和作用,通俗易懂,膜拜大神。
实际上Workerman类似一个PHP版本的nginx,核心也是多进程+Epoll+非阻塞IO。Workerman每个进程能维持上万并发连接。由于本身常住内存,不依赖Apache、nginx、php-fpm这些容器,拥有超高的性能。同时支持TCP、UDP、UNIXSOCKET,支持长连接,支持Websocket、HTTP、WSS、HTTPS等通讯协以及各种自定义协议。拥有定时器、异步socket客户端、异步Mysql、异步Redis、异步Http、异步消息队列等众多高性能组件。
官网地址(http://doc.workerman.net/)
ubuntu@VM-156-73-ubuntu:/usr/local/nginx/conf/vhost$ more test.com.cn.conf
upstream websocket1{
server localhost:19988;
}
server
{
listen 80;
listen 443;
#listen [::]:80;
server_name test.com.cn ;
index index.html index.htm index.php default.html default.htm default.php;
root /data/www/zc2api.zhongchengbus.com.cn/public;
charset utf-8;
ssl on;
ssl_certificate /usr/local/nginx/conf/cert/1_test.com.cn_bundle.crt;
ssl_certificate_key /usr/local/nginx/conf/cert/2_test.com.cn.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
include other.conf;
#error_page 404 /404.html;
# Deny access to PHP files in specific directory
#location ~ /(wp-content|uploads|wp-includes|images)/.*\.php$ { deny all; }
include enable-php.conf;
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 12h;
}
location /wss
{
proxy_pass http://websocket1;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-real-ip $remote_addr;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
location ~ /.well-known {
allow all;
}
location ~ /.git/
{
deny all;
}
location ~ /\.
{
deny all;
}
#access_log /data/www/test.com.cn/logs/access.log;
error_log /data/www/test.com.cn/logs/error.log;
}
reusePort=true)
*/
$worker->count = 1;
//$worker进程启动后创建一个text Worker,以便打开一个内部通讯端口
$worker->onWorkerStart = function($worker)
{
//开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$inner_text_worker = new Worker('text://0.0.0.0:19989');
$inner_text_worker->onMessage=function($connection,$buffer)
{
//$data数组格式,里面有uid,表示向那个uid的页面推送数据
$data = json_decode($buffer,true);
$uid = $data['uid'];
//通过workerman,向uid的页面推送数据
$ret = sendMessageByUid($uid,$buffer);
//返回推送结果
$connection->send($ret?'ok':'fail');
};
$inner_text_worker->listen();
};
//新增一个属性,用来保存uid到connection的映射
$worker->uidConnections = array();
//当有客户端发来消息时执行的回调函数(微信小程序的话,可以接受order_no(或者openid)作为uid传过来)
$worker->onMessage = function($connection,$data)
{
global $worker;
$date = date('Y-m-d H:i:s',time());
file_put_contents('./workerman.log',$date.PHP_EOL,FILE_APPEND );
file_put_contents('./workerman.log',$data.PHP_EOL,FILE_APPEND );
file_put_contents('./workerman.log',$date.PHP_EOL,FILE_APPEND );
$data = json_decode($data,true);
if(!isset($connection->uid))
{
//没验证的话把第一个包当做uid
$connection->uid = $data['uid'];
//保存uid到connection映射,这样可以方便的通过uid查找connection,实现针对特定的uid推送数据
$worker->uidConnections[$connection->uid] = $connection;
return;
}
};
//当有客户端连接断开时,删除映射
$worker->onClose = function($connection)
{
global $worker;
if(isset($connection->uid))
{
//连接断开时,删除映射
unset($worker->uidConnections[$connection->uid]);
}
};
//向所有验证的用户推送数据
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}
//针对uid推送数据
function sendMessageByUid($uid,$message)
{
global $worker;
if(isset($worker->uidConnections[$uid]))
{
$connection = $worker->uidConnections[$uid];
$connection->send($message);
return true;
}
return false;
}
//运行所有worker
Worker::runAll();
注:检票接口处理检票逻辑后调用, 将检票结果推送到小程序
'oskf64usrJ8-Lk99ZYAI-zd3EugM','status' => 'success','msg' => '检票成功');//检票之后传过来的数据
//发送数据,注意19989端口是text协议的端口,text协议需要在数据末尾加上换行符
fwrite($client,json_encode($data)."\n");
//读取推送结果
echo fread($client,8192);
(1)小程序前端
注:小程序已经自动实现了心跳传输
//建立websocket连接//
startWebSocket: function () {
var that = this;
that.WebSocketInit()
wx.onSocketError(function () {
})
wx.onSocketMessage(function (data_) {
//that.queryUnCheckinTicketList();
})
wx.onSocketClose(function (res) {
that.WebSocketInit()
})
},
WebSocketInit: function () {
var that = this;
wx.connectSocket({
url: that.data.wws,//wss://test.com.cn/wss
data: {},
method: 'GET',
success: function (res) {
console.log("connectSocket 成功")
},
fail: function (res) {
console.log("connectSocket 失败")
}
})
wx.onSocketOpen(function () {
// callback
var uid = that.data.openid;
console.log('uid '+uid);
var mCmd = { "uid": uid }
wx.sendSocketMessage({
data: JSON.stringify(mCmd),
success: function (res) {
console.log("sendSocketMessage 成功")
},
fail: function (res) {
console.log("sendSocketMessage 失败")
}
});
wx.onSocketMessage(function (data) {
console.log("onSocketMessage ", data)
that.queryUnCheckinTicketList();
})
})
},
(2)jQuery前端
注: 这段代码可以用来测试websocket服务是否成功
ws = new WebSocket("wss://test.com.cn/wss");
ws.onopen = function() {
alert("连接成功");
ws.send('tom');
alert("给服务端发送一个字符串:tom");
};
ws.onmessage = function(e) {
alert("收到服务端的消息:" + e.data);
};