# 安裝服務
~~~
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、模板標簽