大家好,又见面了,我是全栈君。
(文末有福利)
websocket通知流程 解析
我们不能保证用户B和用户A都处于连接状态,但是通常情况下,用户B至少是连接状态,用户A不一定跟server保持连接;
任一用户都不止对应一个客户端。换言之,用户A和用户B都可能打开了多个tab页,对于一个tab页,就会有一个独立的fd标识,我们这里认为任一用户只有最新的fd有效,或者你可以认为用户所有的tab页的连接都有效;
因为没有用户系统,我们以get传递的参数uid为标识,uid=100视为用户A,uid=101视为用户B;
我们模拟的tab页包将会包含一个输入内容的文本框、一个输入目标uid的input和一个发送的按钮以满足需求。
操作流程分析:
-
用户A($_GET[‘uid’] = 100)在某个tab页的输入框输入”回复xxx的内容”字样后,点击发送
-
用户B($_GET[‘uid’] = 101)如果处于连接状态,则alert提醒用户B,他的评论被回复了
Server
后端启动:php server.php
class CommentServer
{
private $_serv;
public $key = '^manks.top&swoole$';
// 用户id和fd对应的映射,key => value,key是用户的uid,value是用户的fd
public $user2fd = [];
public function __construct()
{
$this->_serv = new swoole_websocket_server("127.0.0.1", 9501);
$this->_serv->set([
'worker_num' => 1,
'heartbeat_check_interval' => 60,
'heartbeat_idle_time' => 125,
]);
$this->_serv->on('open', [$this, 'onOpen']);
$this->_serv->on('message', [$this, 'onMessage']);
$this->_serv->on('close', [$this, 'onClose']);
}
/**
* @param $serv
* @param $request
* @return mixed
*/
public function onOpen($serv, $request)
{
// 连接授权
$accessResult = $this->checkAccess($serv, $request);
if (!$accessResult) {
return false;
}
// 始终把用户最新的fd跟uid映射在一起
if (array_key_exists($request->get['uid'], $this->user2fd)) {
$existFd = $this->user2fd[$request->get['uid']];
$this->close($existFd, 'uid exists.');
$this->user2fd[$request->get['uid']] = $request->fd;
return false;
} else {
$this->user2fd[$request->get['uid']] = $request->fd;
}
}
/**
* @param $serv
* @param $frame
* @return mixed
*/
public function onMessage($serv, $frame)
{
// 校验数据的有效性,我们认为数据被`json_decode`处理之后是数组并且数组的`event`项非空才是有效数据
// 非有效数据,关闭该连接
$data = $frame->data;
$data = json_decode($data, true);
if (!$data || !is_array($data) || empty($data['event'])) {
$this->close($frame->fd, 'data format invalidate.');
return false;
}
// 根据数据的`event`项,判断要做什么,`event`映射到当前类具体的某一个方法,方法不存在则关闭连接
$method = $data['event'];
if (!method_exists($this, $method)) {
$this->close($frame->fd, 'event is not exists.');
return false;
}
$this->$method($frame->fd, $data);
}
public function onClose($serv, $fd)
{
echo "client {$fd} closed.\n";
}
/**
* 校验客户端连接的合法性,无效的连接不允许连接
* @param $serv
* @param $request
* @return mixed
*/
public function checkAccess($serv, $request)
{
// get不存在或者uid和token有一项不存在,关闭当前连接
if (!isset($request->get) || !isset($request->get['uid']) || !isset($request->get['token'])) {
$this->close($request->fd, 'access faild.');
return false;
}
$uid = $request->get['uid'];
$token = $request->get['token'];
// 校验token是否正确,无效关闭连接
if (md5(md5($uid) . $this->key) != $token) {
$this->close($request->fd, 'token invalidate.');
return false;
}
return true;
}
/**
* @param $fd
* @param $message
* 关闭$fd的连接,并删除该用户的映射
*/
public function close($fd, $message = '')
{
// 关闭连接
$this->_serv->close($fd);
// 删除映射关系
if ($uid = array_search($fd, $this->user2fd)) {
unset($this->user2fd[$uid]);
}
}
public function alertTip($fd, $data)
{
// 推送目标用户的uid非真或者该uid尚无保存的映射fd,关闭连接
if (empty($data['toUid']) || !array_key_exists($data['toUid'], $this->user2fd)) {
$this->close($fd);
return false;
}
$this->push($this->user2fd[$data['toUid']], ['event' => $data['event'], 'msg' => '收到一条新的回复.']);
}
/**
* @param $fd
* @param $message
*/
public function push($fd, $message)
{
if (!is_array($message)) {
$message = [$message];
}
$message = json_encode($message);
// push失败,close
if ($this->_serv->push($fd, $message) == false) {
$this->close($fd);
}
}
public function start()
{
$this->_serv->start();
}
}
$server = new CommentServer;
$server->start();
前端页面 client.php
<div>
发送内容:<textarea name="content" id="content" cols="30" rows="10"></textarea><br>
发送给谁:<input type="text" name="toUid" value="" id="toUid"><br>
<button onclick="send();">发送</button>
</div>
<script>
var ws = new WebSocket("ws://127.0.0.1:9501?uid=<?php echo $uid ?>&token=<?php echo $token; ?>");
ws.onopen = function(event) {
};
ws.onmessage = function(event) {
var data = event.data;
data = eval("("+data+")");
if (data.event == 'alertTip') {
alert(data.msg);
}
};
ws.onclose = function(event) {
console.log('Client has closed.\n');
};
function send() {
var obj = document.getElementById('content');
var content = obj.value;
var toUid = document.getElementById('toUid').value;
ws.send('{"event":"alertTip", "toUid": '+toUid+'}');
}
</script>
一步一步教你操作
回复关键字获取资源链接:
wokerman实战之PHP在线客服:wokerman
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111875.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...