# 安装服务
~~~
composer require topthink/think-swoole=2.0.*
~~~
# 创建执行单文件
shurima.php
```php
<?php
class WebSocketServer
{
public $server;
public $tcp;
private $tag = 'swoole_user_sessions';
public function __construct()
{
$this->clearFds();// 开启服务前,清除之前建立的fd用户信息,验证信息保留
$this->server = new Swoole\WebSocket\Server("0.0.0.0", 9001, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$this->server->set([
//'ssl_cert_file' => '/www/server/panel/vhost/cert/hx.toushizhiku.com/fullchain.pem',
//'ssl_key_file' => '/www/server/panel/vhost/cert/hx.toushizhiku.com/privkey.pem',
'ssl_cert_file' => __DIR__.'/config/fullchain.pem',
'ssl_key_file' => __DIR__.'/config/privkey.pem',
'heartbeat_idle_time' => 600,// 表示一个连接如果600秒内未向服务器发送任何数据,此连接将被强制关闭
'heartbeat_check_interval' => 60,// 表示每60秒遍历一次
'worker_num' => 4,
'max_conn' => 50,// 最大连接数,
'log_file' => __DIR__.'/logs/swoole/log.txt',
'log_level' => 0,
'log_rotation' => SWOOLE_LOG_ROTATION_DAILY
]);
$this->tcp = $this->server->listen("0.0.0.0", 9002, SWOOLE_SOCK_TCP);
$this->tcp->set([
//'open_http_protocol' => true,
]);
$this->server->on('WorkerStart', function ($server, $worker_id){
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$server->redis = $redis;
});
$this->server->on('handshake', function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) {
$redis = $this->server->redis;
$tag = $this->tag;
// websocket握手连接算法验证
$secWebSocketKey = $request->header['sec-websocket-key'];
$patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
$response->end();
return false;
}
echo 'sec-key_'.$request->header['sec-websocket-key']."\n";
// 验证用户
$fd = $request->fd;
$session_id = $request->cookie['PHPSESSID'];
echo 'handshake_'.$fd.'_sess_'.$session_id.'_'.json_encode($request->server['remote_addr'])."\n";
if (!$fd || empty($session_id)) {
$response->end();
return false;
}
if (!$redis->hExists($tag, $session_id)) {
$response->end();
return false;
}
$sess_data = json_decode($redis->hGet($tag, $session_id), true);
echo 'handshake_sess_'.json_encode($sess_data).'_time_'.time()."\n";
if (!is_array($sess_data) || !isset($sess_data['user_id']) || !isset($sess_data['create_time'])) {
$response->end();
return false;
}
if ((time() - $sess_data['create_time']) > 86400 || time() < $sess_data['create_time']) {
$response->end();
return false;
}
$sess_data['fd'] = $fd;
$redis->hSet($tag, $session_id, json_encode($sess_data));
unset($sess_data, $fd, $session_id, $tag);
$key = base64_encode(
sha1(
$request->header['sec-websocket-key'] . '344EAFA5-E858-257A-95CA-C7AB0DC85B22',
true
)
);
$headers = [
'Upgrade' => 'websocket',
'Connection' => 'Upgrade',
'Sec-WebSocket-Accept' => $key,
'Sec-WebSocket-Version' => '13',
];
// WebSocket connection to 'ws://127.0.0.1:9001/'
// failed: Error during WebSocket handshake:
// Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket
if (isset($request->header['sec-websocket-protocol'])) {
$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
}
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
// onHandShake 中必须调用 response->status() 设置状态码为 101 并调用 response->end() 响应,否则会握手失败
$response->status(101);
$response->end();
});
$this->server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
$server->push($frame->fd, json_encode(['msg' => 'Hello, welcome.']));
});
$this->tcp->on('receive', function ($server, $fd, $reactor_id, $data) {
echo 'receive_'.json_encode($fd).'_'.json_encode($data, 320).'_'.json_encode($reactor_id)."\n";
// 仅遍历 9001 端口的连接,因为是用的$server,不是$tcp
// 获取tcp连接的信息
$data = json_decode($data, true);
if (!empty($data) && isset($data['title']) && isset($data['user_ids'])) {
$websocket = $server->ports[0];
$redis = $server->redis;
$user_data = $redis->hGetAll($this->tag);
$fds = [];
$tmp = [];
if (is_array($user_data)) {
foreach ($user_data as $key => $user) {
echo 'receive_user_'.$key.'_'.json_encode($user)."\n";
$tmp = json_decode($user, true);
$create_time = !empty($tmp) && is_array($tmp) && isset($tmp['create_time']) ? $tmp['create_time'] : 0;
if (!isset($tmp['user_id']) || (time() - $create_time > 86400) || time() < $create_time) {
$redis->hDel($this->tag, $key);
}
if (isset($tmp['fd']) && in_array($tmp['user_id'], $data['user_ids'])) {
array_push($fds, $tmp['fd']);
}
}
}
foreach ($websocket->connections as $_fd) {
if (!in_array($_fd, $fds)) {
continue;
}
if ($server->exist($_fd)) {
$server->push($_fd, json_encode(['title' => $data['title']], 320));
}
}
unset($user_data, $fds, $tmp);
// 对端不接收信息直接关闭,不再回复
//$server->send($fd, json_encode(['code' => 200, 'msg' => 'success']));
} else {
// 对端不接收信息直接关闭,不再回复
//$server->send($fd, json_encode(['code' => 0, 'msg' => 'params less']));
}
});
$this->server->on('close', function ($ser, $fd) {
echo "client {$fd} closed\n";
});
$this->server->on('WorkerStop', function ($server, $worker_id){
$server->redis->close();
});
$this->server->start();
}
public function clearFds()
{
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$tag = $this->tag;
if (!$redis->exists($tag)) {
$redis->close();
return true;
}
$datas = $redis->hGetAll($tag);
if (is_array($datas)) {
foreach ($datas as $key => $data) {
$tmp = json_decode($data, true);
$create_time = !empty($tmp) && is_array($tmp) && isset($tmp['create_time']) ? $tmp['create_time'] : 0;
if (!isset($tmp['user_id']) || (time() - $create_time > 86400) || time() < $create_time) {
$redis->hDel($tag, $key);
}
unset($tmp['fd']);
$redis->hSet($tag, $key, json_encode($tmp));
}
}
$redis->close();
return true;
}
}
new WebSocketServer();
```
命令行执行:
> php shurima.php
# 前端js
~~~
<script>
var url = 'wss://aaa.test.com:9001';
var websocket, timer, max = 500, count = 0;
var lockConnect = false;
var connection = function () {
websocket = new WebSocket(url);
websocket.onopen = onopen;
websocket.onmessage = onmessage;
websocket.onclose = onclose;
websocket.onerror = onerror;
}
var onopen = function () {
console.log("Connected to WebSocket server.");
websocket.send("Hello!");
ping();
}
var onmessage = function (evt) {
console.log('Retrieved data from server: ' + evt.data);
if (evt.data.length) {
var msg_data = JSON.parse(evt.data);
console.log('message2', msg_data, typeof msg_data)
if (msg_data.title == 'undefined' || msg_data.title == null || msg_data.title == '') {
console.log('message', msg_data.title)
return false;
}
notifyMsg('光年分配任务', msg_data.title);
}
ping();
}
var onclose = function () {
console.log("connection is closed");
reconnection();
}
var onerror = function (evt, e) {
console.log('Error occured: ' + evt, e);
reconnection();
}
var pingCheck, pongCheck;
var ping = function () {
pingCheck && clearTimeout(pingCheck);
pongCheck && clearTimeout(pongCheck);
pingCheck = setTimeout(function () {
websocket.send('ping');
pongCheck = setTimeout(function () {
websocket.close();
}, 3500)
}, 60000);
}
var reconnection = function () {
// 0 CONNECTING,1 OPEN,2 CLOSING,3 CLOSED
if (lockConnect) {
return false;
}
lockConnect = true;
count = count + 1;
console.log("reconnection " + count + " times and readyState is " + websocket.readyState);
timer && clearTimeout(timer);
if (count >= max || websocket.readyState == 1) {
clearTimeout(timer);
count = 0;
} else {
timer = setTimeout(function () {
connection();
lockConnect = false;
}, 30000);
}
}
connection();
function notifyMsg(title, content) {
var options = {
dir: "auto", // 文字方向
body: content,
requireInteraction: true,
tag: "notification",
renotify: true,
icon: "https://file.toushivip.com/msg2.png",
};
// 先检查浏览器是否支持
if (!window.Notification) {
console.log('浏览器不支持通知');
} else {
console.log('permission', Notification.permission);
// 检查用户曾经是否同意接受通知
if (Notification.permission === 'granted') {
var notification = new Notification(title, options); // 显示通知
notification.onclick = function (event) {
event.preventDefault(); // prevent the browser from focusing the Notification's tab
window.open('/portal/distribution');
}
} else if (Notification.permission === 'default') {
// 用户还未选择,可以询问用户是否同意发送通知
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
var notification = new Notification(title, options); // 显示通知
notification.onclick = function (event) {
event.preventDefault();
window.open('/portal/distribution');
}
} else if (permission === 'default') {
console.warn('用户关闭授权 未刷新页面之前 可以再次请求授权');
} else {
// denied
console.log('用户拒绝授权 不能显示通知');
}
});
} else {
// denied 用户拒绝
console.log('用户曾经拒绝显示通知');
}
}
}
</script>
~~~
- thinkphp
- thinkphp笔记
- 后台登陆退出
- config配置
- 隐藏后台模块
- 单独调用腾讯云行为验证码
- api接口跨域问题
- api接口创建案例代码
- 使用gateway worker
- 使用swoole代码笔记
- 使用队列 think-queue笔记
- 后台布局
- MySQL
- 1、关于lnmp mysql的一个坑
- 2、mysql实现group by后取各分组的最新一条
- 其他
- 搞笑的注释代码
- 分页类
- nodejs 打包网址为exe
- 免费天气预报API接口
- Ajax
- 简单的ajax分页1
- 通用ajax-post提交
- 引用的类库文件
- Auth.php
- Auth.php权限控制对应的数据库表结构
- Layui.php
- Pinyin.php
- Random.php
- Tree.php
- Tree2.php
- Js-Jq
- Git的使用
- 3、bootstrap-datetimepicker实现两个时间范围输入
- CentOS安装SSR做梯子
- Python爬虫
- 1、安装Gerapy
- 2、安装Scrapy
- 3、Scrapy使用
- 4、Scrapy框架,爬取网站返回json数据(spider源码)
- 0、Python pip更换国内源(一句命令换源)
- 服务器运维
- 1、宝塔使用webhook更新服务器代码
- 2、搭建内网穿透
- 3、数据库主从同步
- 4、数据库复制
- hui-Shop问题
- 1、前端模板的注意事项
- 2、模板标签