//swoole 安装
/*
* git clone https://github.com/swoole/swoole-src.git
*
* cd swoole-src
*
* phpize
*
* ./configure --with-php-config=/www/server/php/71/bin/php-config --enable-openssl
*
* make && sudo make install
*
* php --ini -----/www/server/php/71/etc/php.ini
*
* /www/server/php/71/etc/php.ini 中加入 extension=swoole.so
*
* 重启php
* */
<?php
declare(strict_types=1);
use Swoole\Coroutine;
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Runtime;
use JPush\Client as JPush; //激光推送
class WebSocketServer{
protected $serv = null; //WebSocket
protected $server = null; //tcp 监听
protected $pool = null; //pdo链接池
protected $host = '0.0.0.0'; //监听对应外网的IP 0.0.0.0监听所有ip
protected $port = 9306; //端口号
protected $server_port = 9307; //监听端口号
protected $Push = null; //苹果 / 安卓 推送
public function __construct(){
//创建websocket服务器对象
$this->serv = new Swoole\WebSocket\Server($this->host, $this->port,SWOOLE_PROCESS);
//设置参数
//如果业务代码是全异步 IO 的,worker_num设置为 CPU 核数的 1-4 倍最合理
//如果业务代码为同步 IO,worker_num需要根据请求响应时间和系统负载来调整,例如:100-500
//假设每个进程占用 40M 内存,100 个进程就需要占用 4G 内存
$this->serv->set(array(
'worker_num' => 8, //设置启动的worker进程数。【默认值:CPU 核数】
'max_request' => 100000, //设置每个worker进程的最大任务数。【默认值:0 即不会退出进程】
'daemonize' => false, //开启守护进程化【默认值:0】
'heartbeat_idle_time' => 600, // 表示一个连接如果60秒内未向服务器发送任何数据,此连接将被强制关闭
'heartbeat_check_interval' => 60, // 表示每20秒遍历一次
'tcp_fastopen' => true //快速握手
));
$this->server = $this->serv->listen($this->host, $this->server_port,SWOOLE_SOCK_TCP);
$this->server->set([
'open_eof_split' => true,
'package_eof' => "\n",
'buffer_output_size'=>32 * 1024 * 1024,
'tcp_fastopen' => true, //快速握手
]);
// 创建连接池对象,默认创建64个连接
$this->pool = new PDOPool((new PDOConfig)
->withHost('0.0.0.0')
->withPort(3306)
->withDbName('***')
->withCharset('utf8mb4')
->withUsername('***')
->withPassword('***')
);
$this->serv->on('WorkerStart', function ($request, $response) {
$pdo = $this->pool->get();
//清空 web fd
if ($pdo->query("select * from ***")->fetchAll()){
$pdo->exec("DELETE FROM **");
}
//清空 tcp fd
if ($pdo->query("select * from **")->fetchAll()){
$pdo->exec("DELETE FROM ***");
}
$this->pool->put($pdo);
var_dump("启动进程(".$response.")").PHP_EOL;
});
//监听WebSocket连接打开事件
$this->serv->on('open', function ($serv, $open) {
echo "WebSocket 建立连接: {$open->fd}" . PHP_EOL;
});
//监听WebSocket消息事件
//客户端向服务器端发送信息时,服务器端触发 onMessage 事件回调
//服务器端可以调用 $server->push() 向某个客户端(使用 $fd 标识符)发送消息,长度最大不得超过 2M
$this->serv->on('message', function ($ws, $request) {
try {
}catch (Exception $e){
$this->DeBugLog(array('title'=>'web:上线(异常)','fd'=>$request->fd,'err'=>$e));
}
});
//监听WebSocket连接关闭事件
$this->serv->on('close', function ($serv, $fd) {
echo "Websocket: close - {$fd}" . PHP_EOL;
try {
//下线处理
}catch (Exception $e){
$this->DeBugLog(array('title'=>'web:下线(异常)','fd'=>$fd,'err'=>$e));
}
});
//------------------------- tcp -------------------------------------------------- tcp -------------------------------------------------------------------------------
//设置每个port的回调函数
$this->server->on('connect', function ($serv, $fd){
echo "Tcp 建立连接: {$fd}" . PHP_EOL;
});
$this->server->on('receive', function ($serv, $fd, $from_id, $data) {
try {
//数据处理
}catch (Exception $e){
$this->DeBugLog(array('title'=>'tcp:上线(异常)','mac'=>$mac,'err'=>$e));
}
});
$this->server->on('close', function ($serv, $fd) {
try {
//下线处理
}catch (Exception $e){
$this->DeBugLog(array('title'=>'tcp:下线(异常)','fd'=>$fd,'err'=>$e));
}
});
//启动服务
$this->serv->start();
}
//错误记录
function DeBugLog($content = []){
$pdo = $this->pool->get();
$pdo->exec("insert into *** (addtime,content) values ('".time()."','".json_encode($content,JSON_OBJECT_AS_ARRAY)."')");
$this->pool->put($pdo);
}
}
new WebSocketServer();
<!--服务操作-->
<script>
var key = 'ç∂ßZ6Zu36Zu¿-.-^*^/@/sll';
var wsServer = 'wss://域名:端口';
var lockReconnect = false; //避免wss重复连接
var ws = null; // 判断当前浏览器是否支持WebSocket
var wsUrl = wsServer; //https设置wss,http设置ws
createWebSocket(wsUrl); //连接ws
//验证浏览器是否支持
function createWebSocket(url) {
try{
if('WebSocket' in window){
ws = new WebSocket(url);
}else if('MozWebSocket' in window){
ws = new MozWebSocket(url);
}else{
layui.use(['layer'],function(){
var layer = layui.layer;
layer.alert("您的浏览器不支持websocket协议,建议使用新版谷歌、火狐等浏览器,请勿使用IE10以下浏览器,360浏览器请使用极速模式,不要使用兼容模式!");
});
}
initEventHandle();
}catch(e){
reconnect(url);
console.log(e);
}
}
// 服务连接及数据处理
function initEventHandle() {
ws.onclose = function () {
reconnect(wsUrl);
console.log("ws连接关闭!" + new Date().toUTCString());
alert('服务器连接已关闭');
};
ws.onerror = function () {
reconnect(wsUrl);
console.log("ws连接错误!");
alert('服务器失去连接');
};
ws.onopen = function () {
//心跳检测重置
heartCheck.reset().start();
//获取用户id
var id = $('#id').val();
//首次连接成功把id发送过去保存
ws.send(key+id);
alert('已成功连接服务器');
//连接时间:new Date().toUTCString() 国际标准时间 GMT 时间
console.log("ws连接成功!" + new Date().toUTCString());
};
ws.onmessage = function (event) { //如果获取到消息,心跳检测重置
heartCheck.reset().start(); //拿到任何消息都说明当前连接是正常的
console.log("ws收到消息啦:" + event.data);
if (event.data != key) {
//上线:失败
if (event.data == 'error'){
alert('上线失败,请刷新页面重试');
}
//消息发送失败
if (event.data == 'timeout'){
alert('发送失败');
}
//接收好友发送的数据
if (event.data.substr(0,4) == 'send'){ //接收到的消息
//获取好友id
var FriendId = $('#FriendId').val();
//获取内容
var info = event.data.substr(5);
if (info != ''){
//解析json数据
var datas = $.parseJSON(info);
console.log(datas);
}
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
ws.close();
}
}
}
// 重连
function reconnect(url) {
if (lockReconnect) return;
lockReconnect = true;
setTimeout(function () { //没连接上会一直重连,设置延迟避免请求过多
createWebSocket(url);
lockReconnect = false;
}, 2000);
}
//心跳检测
var heartCheck = {
timeout: 30000, //半分钟发一次心跳
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(key);
console.log(new Date().toUTCString()+':兄弟出来嗨!!!');
//如果超过一定时间还没重置,说明后端主动断开了
self.serverTimeoutObj = setTimeout(function () {
//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
ws.close();
}, self.timeout)
}, this.timeout)
}
}
//发送消息
ws.send('send-'+data.value.fid+':'+content); //发送消息
</script>