workman官方文档http://doc3.workerman.net/640361
1.将推送的端口号在服务器开放,阿里云在安全组规则里进行添加
2.本人使用的是宝塔,用宝塔的小伙伴还需要再宝塔面板的安全中,放行端口号
3.php.ini中的extension=php_sockets.dll 扩展打开,(去掉前面的;即可)
4.查看phpinfo中的socket是否是enable
以上这些确定之后就可以正式开始开发socket了~
服务端
安装workman
composer require workerman/workerman
执行php artisan
执行php artisan make:command orderSocketCommand 在app\Console\Commands生成orderSocketCommand .php文件
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Workerman\Worker;
use Workerman\Lib\Timer;
use Log;
class orderSocketCommand extends Command
{
protected $signature = 'orderSocketCommand {action} {-d?}';#若果加上该选项,则以守护进程运行
protected $description = 'Start a orderSocketCommand server';
protected $heartbeatTime = 60;#消息间隔
public function __construct() {
parent::__construct();
}
public function handle() {
global $argv;
global $worker;
// argv[0] 默认是当前文件,可以不修改
$argv[1] = $argv[2];
$argv[2] = isset($argv[3]) ? "-{$argv[3]}" : '';
// 创建一个Worker监听6666端口,不使用任何应用层协议
$worker = new Worker("websocket://0.0.0.0:6666");
$arg = $this->argument('action');
switch ($arg) {
case 'start':
$this->start($worker);
break;
case 'stop':
$this->stop($worker);
break;
}
}
private function start($worker) {
// 启动1个进程对外提供服务
$worker->count = 1;
// worker进程启动后建立一个内部通讯端口
$worker->onWorkerStart = function($worker){
// 每10秒执行一次------需要加心跳检测的打开这个注释,并修改自己的逻辑
// $timeInterval = 10;
// Timer::add($timeInterval, function() use ($worker) {
// $timeNow = time();
// foreach ($worker->connections as $connection) {
// // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
// if (empty($connection->lastMessageTime)) {
// $connection->lastMessageTime = $timeNow;
// }
// // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
// if ($timeNow - $connection->lastMessageTime > $this->heartbeatTime) {
// // 检测客户端是否断开,客户端是否回应,未回应断开
// $connection->close();
// }
// }
// });
// 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
$inner_text_worker = new Worker('text://0.0.0.0:8888');
$inner_text_worker->onMessage = function($connection, $buffer)
{
global $worker;
// $data数组格式,里面有orderType,表示向那个orderType的页面推送数据
$data = json_decode($buffer, true);
$orderType = $data['orderType'];
// 通过workerman,向orderType的页面推送数据
$result = $this->sendMessageByorderType($orderType, $buffer);
// 返回给客户端推送结果
$connection->send($result? 'success' : 'fail');
};
$inner_text_worker->listen();
};
// 新增加一个属性,用来保存orderType到connection的映射
$worker->orderTypeConnections = array();
// 当有客户端发来消息时执行的回调函数
$worker->onMessage = function($connection, $data)use($worker) {
// 判断当前客户端是否已经验证,既是否设置了orderType
if(!isset($connection->orderType)) {
Log::info("Command orderSocketCommand 当有客户端发来orderType消息时执行的回调函数中的send data:".$data);
$data = json_decode($data,true);
$type = $data['orderType'];
$connection->orderType = $data['uid'] .'-'. $data['orderType'];
// 保存orderType到connection的映射,这样可以方便的通过orderType查找connection,实现针对特定orderType推送数据
$worker->orderTypeConnections[$type][$connection->orderType] = $connection;
$connection->send(json_encode($data));
return;
}else{
Log::info("Command orderSocketCommand 客户端发来的所有消息:".json_encode($data));
$connection->send($data);
return;
}
};
Worker::runAll();
}
// 针对orderType推送数据
private function sendMessageByorderType($orderType, $message) {
global $worker;
foreach($worker->orderTypeConnections[$orderType] as $connection) {
$connection->send($message);
Log::info("Command orderSocketCommand 一个内部端口,方便内部系统推送数据send data:".json_encode($message));
}
return true;
}
private function stop($worker) {
$worker->reloadable = false;
$worker->onClose = function($connection) {
echo "orderSocketCommand stop";
};
// 运行worker
Worker::runAll();
}
}
Kernel.php中添加
protected $commands = [
......
Commands\orderSocketCommand::class
];
运行
php artisan workerman start d
Workerman[artisan] start in DEBUG mode
----------------------- WORKERMAN -----------------------------
Workerman version:3.5.22 PHP version:7.1.13
------------------------ WORKERS -------------------------------
user worker listen processes status
root none websocket://0.0.0.0:666 1 [OK]
----------------------------------------------------------------
Start success
这样服务端就写好了,官方文档要好好看真的很有用
其中connection的映射这一块一定要仔细看官网的例子:
WorkerMan中如何向某个特定客户端发送数据http://doc3.workerman.net/315238
客户端
将需要的数据信息推送到服务端(我写的数据逻辑之后,可以公共调用)
namespace App\Models;
use Log;
class Common{
public static function push($data){
// 建立socket连接到内部推送端口
$client = stream_socket_client('tcp://127.0.0.1:8888', $errno, $errmsg, 1);//本地测试
if (!$client) {
throw new \Exception("{$errstr} ({$errno})", 1);
}
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data)."\n");
// 读取推送结果
$result = fread($client, 4096);
fclose($client);
return $result;
}
}
客户端接收数据
var ws = null;
connect();
function connect() {
// 创建一个 websocket 连接
ws = new WebSocket("ws://127.0.0.1:6666");//本地测试
// websocket 创建成功事件
ws.onopen = onopen;
// websocket 接收到消息事件
ws.onmessage = onmessage;
ws.onclose = onclose;
ws.onerror = onerror;
}
function onopen(){
var orderType = document.querySelector("input#type").value;//这个用来区分socket的不同数据
var uid = document.querySelector("input#uid").value;
var data = {'orderType':orderType,'uid':uid}
ws.send(JSON.stringify(data));
heartCheck.reset().start(); //心跳检测重置
}
// 接受服务端数据是触发事件
function onmessage(e) {
heartCheck.reset().start(); //拿到任何消息都说明当前连接是正常的
// socket推送的数据属于string类型,将其转换成json格式
var data = e.data;
data = JSON.parse(data);
if (data.hasOwnProperty('uid')) {
console.log('不是所需要的数据')
}else {
if (typeof(data) != 'number') {//心跳检测的数据是999,这是不需要的数据
var _token=$('meta[name=token]').attr('content');
$.ajax({
type: "post",
url: "/sys/order/deal_data",
data: {'_token':_token,'type':data.orderType},
dataType: 'JSON',
success: function(json) {
var html = ''
for (var i = 0; i < json.data.length; i++) {
html += ``
if (json.data[i].users == null) {
html += ``
}else{
html += `` + json.data[i].users['username'] + `
}
html += `` + json.data[i].number + ``
}
var content = document.querySelector('#content');
content.innerHTML = html
// 自动播放铃声
var mp3 = "/music/test.mp3";
var mp3 = new Audio(mp3);
mp3.play(); //播放音乐提醒有新订单
}
});
}
}
}
function onclose(){}
function onerror(){}
//心跳检测
var heartCheck = {
timeout: 300000, //五分钟发一次心跳
timeoutObj: null,
serverTimeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function(){
var self = this;
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
ws.send("9999");
self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了
ws.close(); //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
}, self.timeout)
}, this.timeout)
}
}
这样客户端接收到数据的时候就会播放指定提示音了~
客户端的心跳检测参考:https://www.jianshu.com/p/1141dcf6de3e