Swoole实现Websocket主动推送
需求:
需要每秒钟向前台推送一次行情数据。
向某个用户推送消息
及时响应前端发出的请求
握手时效验令牌
每个ip 不超过十个连接
因为网上没有很具体的相关文档,只能摸着石头过河,现功能已经大致实现,如果有应用不合理的地方希望大神不吝赐教!
$config = require('/config/swoole.php');
class Websocket {
public $server;
private $config;
public function __construct($config) {
$this->config = $config;
$server = new Swoole\WebSocket\Server($this->config['web_host'],$this->config['web_port']);
$this->server = $server;
$this->server->set([
'worker_num' => 8,
'daemonize' => 1,
'backlog' => 128,
'max_request' => 10000,
'heartbeat_check_interval' => 60,
'task_worker_num' => 24,
]);
$process = new Swoole\Process(function($process) use ($server) {
while (true) {
$show_market_list = $this->getRealMarket();
if ($show_market_list) {
foreach ($this->server->connections as $conn) {
$this->server->push($conn,json_encode(['type' => 'market','date' => $show_market_list]));
}
}
sleep(1);
}
});
$newprocess = new Swoole\Process(function($process) use ($server){
while (true) {
$redis = $this->newRedis();
$new_list = $redis->hGetALL('swoole_new_push');
if ($new_list) {
//把消息推给所有用户
foreach ($new_list as $k => $v) {
//查找用户的fd 检查用户是否在线
$user_fd = $redis -> hGet('swoole_uid_fd',$k);
if ($user_fd) {
$this->server->push($user_fd,json_encode(['type' => 'new','date' => $v]));
}
}
}
sleep(1);
}
});
$this->server->addProcess($newprocess);
$this->server->addProcess($process);
$this->server->on('handshake',function($request,$response){
$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();
$this->server->close($request->fd);
return 500;
}
$key = base64_encode(sha1(
$request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
true
));
$headers = [
'Upgrade' => 'websocket',
'Connection' => 'Upgrade',
'Sec-WebSocket-Accept' => $key,
'Sec-WebSocket-Version' => '13',
];
if (isset($request->header['sec-websocket-protocol'])) {
//占用ip
$real_ip = $request->header['x-real-ip'];
$result = $this->checkMac(md5($real_ip));
if (!$result) {
$response->end();
$this->server->close($request->fd);
return 500;
}
$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
}else{
$response->end();
$this->server->close($request->fd);
return 500;
}
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
$response->status(101);
$response->end();
$getAllMarket = $this->getAllMarket();
$this->server -> push($request->fd,json_encode($getAllMarket));
});
$this->server->on('open', function (swoole_websocket_server $server, $request) {
});
$this->server->on('task',function(swoole_server $server, $task_id, $from_id, $data){
$task = json_decode($data['type'],1);
switch ($task['type']) {
//K线
case 'kline':
$result['type'] = $task['type'];
$result['kline_time_type'] = $task['kline_time_type'];
$result['market'] = $task['market'];
$result['data'] = $this->getKlineDate($task['kline_time_type'],$task['market']);
$result['history'] = $this->getMarketRecord($task['market']);
$server -> push($data['fd'],json_encode($result));
break;
//用户注册
case 'login':
$token = $task['token'];
$redis = $this->newRedis();
$uid = $redis->hGet('user_token',$token);
if ($uid) {
//找到了 用户的uid
$redis -> hSet('swoole_uid_fd',$uid,$data['fd']);
$redis -> hSet('swoole_fd_uid',$data['fd'],$uid);
$result['type'] = 'login';
$result['code'] = 200;
}else{
$result['type'] = 'login';
$result['code'] = 400;
}
$server -> push($data['fd'],json_encode($result));
break;
//响应消息
case 'respond':
$token = $task['token'];
$redis = $this->newRedis();
$uid = $redis->hGet('user_token',$token);
if ($uid) {
$redis->hDel('swoole_new_push',$uid);
$result['type'] = 'respond';
$result['code'] = 200;
}else{
$result['type'] = 'respond';
$result['code'] = 400;
}
$server -> push($data['fd'],json_encode($result));
break;
default:
break;
}
});
$this->server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
$task['type'] = $frame->data;
$task['fd'] = $frame->fd;
$server -> task($task);
});
$this->server->on('close', function ($server, $fd) {
//删除用户在线
$redis = $this->newRedis();
$uid = $redis -> hGet('swoole_fd_uid',$fd);
if ($uid) {
$redis -> hDel('swoole_uid_fd',$uid);
$redis -> hDel('swoole_fd_uid',$fd);
}
});
$this->server->on('request', function ($request, $response) {
});
$this->server->start();
}
//K线数据
public function getKlineDate($type,$market){
$redis = $this->newRedis();
$date = $redis->hGet($type,$market);
return $date;
}
public function getMarketRecord($market){
$redis = $this->newRedis();
$result['yest_close_price'] = $redis->hget('yest_close_price',$market);
$result['today_open_price'] = $redis->hget('today_open_price',$market);
$result['today_low_price'] = $redis->hget('today_low_price',$market);
$result['today_high_price'] = $redis->hget('today_high_price',$market);
return $result;
}
//获取全部行情
public function getAllMarket(){
$redis = $this->newRedis();
$market_buy_list = $redis -> HgetAll('real_market_buy');
$market_sell_list = $redis -> HgetAll('real_market_sell');
$market_real_list = $redis->HgetAll('real_market');
foreach ($market_real_list as $k => $v) {
$show_market_list[$k]['real'] = $v;
$show_market_list[$k]['sell'] = $market_sell_list[$k];
$show_market_list[$k]['buy'] = $market_buy_list[$k];
}
return $show_market_list;
}
//获取变动的行情 进行广播
public function getRealMarket(){
$redis = $this->newRedis();
$market_buy_list = $redis -> HgetAll('real_market_buy');
$market_sell_list = $redis -> HgetAll('real_market_sell');
$market_real_list = $redis -> HgetAll('real_market');
foreach ($market_real_list as $k => $v) {
$result['real'] = $v;
$result['sell'] = $market_sell_list[$k];
$result['buy'] = $market_buy_list[$k];
$history_market = $redis -> hGet('history_market',$k);
if ($history_market == json_encode($result)) {
//这一次读取的数据 和历史数据是一样的
continue;
}else{
$show_market_list[$k] = $result;
}
}
return $show_market_list;
}
//每个IP 每分钟最多10个连接
public function checkMac($mac,$status = 0){
$zero_time = strtotime(date('Y-m-d H:i'));
$redis = $this->newRedis();
$res = $redis->hGet('Conn_Mac',$mac);
if ($res) {
try {
$res = json_decode($res,1);
if ($res['time'] != $zero_time) {
//如果已经超过了一分钟区间
$json['time'] = $zero_time;
$json['rate'] = 1;
$redis -> Hset('Conn_Mac',$mac,json_encode($json));
return true;
}else{
if ($res['rate'] < 100) {
//如果连接数还没有达到上限
$json = json_encode(['time' => $zero_time,'rate' => $res['rate'] + 1]);
$redis ->Hset('Conn_Mac',$mac,$json);
return true;
}else{
//连接次数达到上线
return false;
}
}
} catch (Exception $e) {
$json['time'] = $zero_time;
$json['rate'] = 1;
$redis ->Hset('Conn_Mac',$mac,json_encode($json));
return true;
}
}else{
$arr['time'] = $zero_time;
$arr['rate'] = 1;
$redis -> hSet('Conn_Mac',$mac,json_encode($arr));
return true;
}
}
public function newRedis(){
$redis = new redis();
$redis -> connect($this->config['read_redis_host'],$this->config['read_redis_prot']);
$redis -> auth($this->config['read_redis_auth']);
return $redis;
}