Laravel下Workerman实现websocket长连接
- 安装workerman
composer require workerman/workerman -vvv
- 创建laravel自定义命令
php artisan make:command Workerman
执行这段命令后会在\app\Console\Commands文件夹下生成Workerman.php文件
- Workerman.php文件代码 :
<?php
namespace App\Console\Commands;
use Workerman\Worker;
use Illuminate\Console\Command;
class Workerman extends Command {
private $server;
// -d 是否以debug方式运行
protected $signature = 'workerman {action} {-d?}';
protected $description = 'Start a Workerman server.';
public function __construct() {
parent::__construct();
}
/** * Execute the console command. * * @return mixed */
public function handle() {
global $argv;
$context = array(
// 更多ssl选项请参考手册 http://php.net/manual/zh/context.ssl.php
'ssl' => array(
// 请使用绝对路径
'local_cert' => '/www/server/panel/vhost/cert/www.example.com/fullchain.pem', // 也可以是crt文件
'local_pk' => '/www/server/panel/vhost/cert/www.example.com/privkey.pem',
'verify_peer' => false,
// 'allow_self_signed' => true, //如果是自签名证书需要开启此选项
)
);
// 这里设置的是websocket协议(端口任意,但是需要保证没被其它程序占用)
$worker = new Worker('websocket://0.0.0.0:2000', $context);
// 设置transport开启ssl,websocket+ssl即wss
$worker->transport = 'ssl';
// $worker->onMessage = function($con, $msg) {
// $con->send('ok');
// };
$this->server = $worker;
// $this->server = new Worker("websocket://0.0.0.0:2000");
$arg = $this->argument('action');
$argv[1] = $argv[2];
$argv[2] = isset($argv[3]) ? "-{$argv[3]}" : '';
switch ($arg) {
case 'start':
$this->start();
break;
case 'stop':
$this->stop();
break;
case 'restart':
$this->restart();
break;
case 'reload':
$this->reload();
break;
}
}
private function start() {
// 启动4个进程对外提供服务
$this->server->count = 4;
$handler = \App::make('handlers\WorkermanHandler');
// 连接时回调
$this->server->onConnect = [$handler, 'onConnect'];
// 收到客户端信息时回调
$this->server->onMessage = [$handler, 'onMessage'];
// 进程启动后的回调
$this->server->onWorkerStart = [$handler, 'onWorkerStart'];
// 断开时触发的回调
$this->server->onClose = [$handler, 'onClose'];
// 运行worker
Worker::runAll();
}
private function stop() {
$worker = $this->server;
// 设置此实例收到reload信号后是否reload重启
$worker->reloadable = false;
$worker->onWorkerStop = function($worker)
{
echo "Worker reload...\n";
};
// 运行worker
Worker::runAll();
}
private function restart() {
$worker = $this->server;
// 设置此实例收到reload信号后是否reload重启
$worker->reloadable = true;
$worker->onWorkerStart = function($worker)
{
echo "Worker restart...\n";
};
// 运行worker
Worker::runAll();
}
private function reload() {
$worker = $this->server;
// 设置此实例收到reload信号后是否reload重启
$worker->reloadable = false;
$worker->onWorkerStart = function($worker)
{
echo "Worker reload...\n";
};
// 运行worker
Worker::runAll();
}
}`
- workerman引用的执行文件
app\Console\Commands\handlers\WorkermanHandler.php文件
<?php
namespace handlers;
use Workerman\Lib\Timer;
use Illuminate\Support\Facades\Redis;
use App\Log\LogController;
// 心跳间隔10秒
define('HEARTBEAT_TIME',100);
class WorkermanHandler {
const check = 0;
// 处理客户端连接
public function onConnect($connection) {
echo "链接成功";
}
public function editLog($function,$param,$res){
$log=new LogController();
// if($this->log == '1'){
$log->Success('接口:'.$function.';上传参数如下:'.json_encode($param).';返回参数:'.json_encode($res));
// }
}
// public function onMessage($connection, $data) {
// $connection->send($data);
// }
// 处理客户端消息
public function onMessage($connection, $data) {
$this->editLog($function='onMessage',$data,'');
//当上来数据获取最后上传数据的时间
$connection->lastMessageTime = time();
// $connection->send($data);
// 心跳包
if($data!='{"t2yp3e":"213213","sae1acr213et":"sadasdas"}'){
//RSA解密
if(self::check == 1){
$myfile = fopen("rsa/rsa_private_key.pem", "r") or die("Unable to open file!");
$private_key = fread($myfile,filesize("rsa/rsa_private_key.pem"));
$pi_key = openssl_pkey_get_private($private_key);
$encrypted = str_replace(' ', '+',$data);
$decrypted = "";
// 解密
openssl_private_decrypt(base64_decode($encrypted),$decrypted,$pi_key);//私钥解密
fclose($myfile);
//解密内容为空
if($decrypted==''){
$connection->send('验证失败');
}
// $connection->send($decrypted);
//数组化参数
$data = json_decode($decrypted);
$this->editLog($function='onMessage',$data,'');
}else{
//测试时候用
$connection->send($data);
$data = json_decode($data);
}
}else{
$connection->send($data);
$data='';
}
if($data!=''){
//执行你想做的事情
}
//故障代码
if(!empty($data->code)){
//执行你想做的事情
}
// 记录速度等数据(详情被激活)可忽略
if(!empty($data->bluetooth)){
$key_detail = 'detail'.$data->bluetooth;
$detail = Redis::get($key_detail);
if($detail){
//曲线图的数据
$key_curve = $data->bluetooth.'curve';
$curve = Redis::get($key_curve);
if(!empty($curve)){
$curve = unserialize($curve);
// echo json_encode($curve);
//数组满多少个元素会自动清除第一个元素
// if(count($curve)>5){
// array_shift($curve);
// }
array_push($curve,['speed'=>$data->speed??'0','voltage'=>$data->voltage??'0','electric_current'=>$data->current??'0','output_power'
=>$data->outpower??'0','Motherboardtemperature'=>$data->mTemperature??'0','Motortemperature'=>$data->eTemperature??'0','createtime'=>date('Y-m-d H:i:s',time())]);
Redis::setex($key_curve, 10, serialize($curve));
}else{
$curve = array();
array_push($curve,['speed'=>$data->speed??'0','voltage'=>$data->voltage??'0','electric_current'=>$data->current??'0','output_power'
=>$data->outpower??'0','Motherboardtemperature'=>$data->mTemperature??'0','Motortemperature'=>$data->eTemperature??'0','createtime'=>date('Y-m-d H:i:s',time())]);
Redis::setex($key_curve, 10, serialize($curve));
}
}else{
echo 'detail is not';
}
$open = Redis::get($data->bluetooth.'open');
if($open == '1'){
$export = Redis::get($data->bluetooth.'export');
if(!empty($export)){
$export = unserialize($export);
array_push($curve,['speed'=>$data->speed??'0','voltage'=>$data->voltage??'0','electric_current'=>$data->current??'0','output_power'
=>$data->outpower??'0','Motherboardtemperature'=>$data->mTemperature??'0','Motortemperature'=>$data->eTemperature??'0','createtime'=>date('Y-m-d H:i:s',time())]);
Redis::setex($data->bluetooth.'export', 300, serialize($export));
}else{
$export = array();
array_push($curve,['speed'=>$data->speed??'0','voltage'=>$data->voltage??'0','electric_current'=>$data->current??'0','output_power'
=>$data->outpower??'0','Motherboardtemperature'=>$data->mTemperature??'0','Motortemperature'=>$data->eTemperature??'0','createtime'=>date('Y-m-d H:i:s',time())]);
Redis::setex($data->bluetooth.'export', 300, serialize($export));
}
}
}
// 激活详情(详情请求websocket时触发)mTemperature
if(!empty($data->bluetooth)){
$key_detail = 'detail'.$data->bluetooth;//为1时,曲线图存储速度等数据
Redis::setex($key_detail,'10', 1);
// 获取曲线图的速度等数据
$key_curve = $data->bluetooth.'curve';
$curve = Redis::get($key_curve);
$connection->send(json_encode(unserialize($curve)));
}
if(!empty($id)){
if(!empty($data->code)&&$data->code!=''){
\DB::table('dlc_websocket')->where('id',$id)->update(['code'=>$data->code]);
}
//id赋值
$connection->id = $id;
}
// }
}
// 处理客户端断开
public function onClose($connection) {
echo "connection closed from ip {$connection->getRemoteIp()}\n";
}
public function onWorkerStart($worker) {
Timer::add(1, function () use ($worker) {
$time_now = time();
foreach ($worker->connections as $connection) {
// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
if($connection->id){
\DB::table('dlc_websocket')->where('id',$connection->id)->delete();
// \DB::table('dlc_websocket')->where('id',$connection->id)->update(['status'=>0]);
}
//断开后的回调
echo "Client ip {$connection->getRemoteIp()} timeout!!!\n"; $connection->close();
}
}
});
}
}
第一个文件是配置workerman的基本信息,可以配置https,长连接的会是wss而不是ws。第二个文件是用于接收到数据进行处理,比如用于存储,前端页面画图展示等。
- 启动workerman
php artisan Workerman start d
- 关闭workerman
php artisan Workerman stop d