举个例子: 用户A和用户B进入直播系统之后 我们需要将所有的直播列表展示出来 当B用户新创建了直播间后A用户界面自动更新无需刷新;
传统做法 js当中定时任务:
const ws = new WebSocket("ws://192.168.1.168:8008/cast");
ws.onopen=function(){
console.log("链接服务器");
getCast();
//定时任务 1.5秒请求一次
window.setInterval(()=>{
getCast();//链接上服务器之后立马获取直播间列表的数据
},1500);
};
定时去请求后台接口返回数据再渲染到前端界面 但是这样比较消耗服务器资源 并且在没有新的直播间创建的同时也会每1.5s请求一次服务器
相当浪费资源有没有更好的解决方式呢? 使用websocket从服务器自动推送数据到前端 看图
撸代码 前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script>
const ws = new WebSocket("ws://192.168.1.168:8008/cast");
ws.onopen=function(){
console.log("链接服务器");
getCast();//首次链接上服务器之后立马获取直播间列表的数据 然后每当数据库有更新的时候ws会自动推送全部的最新的直播间列表数据到前端 从而避免了我们使用js定时器请求后台接口的耗费资源的做法
window.setInterval(()=>{
getCast();//链接上服务器之后立马获取直播间列表的数据
},1500);
};
ws.onmessage=function(event){
if(event.data){
const result = JSON.parse(event.data);//将返回的json字符串对象化
if("data" in result && result.data != null){ //判断data是否在对象里面并且不为空
//不管是刚开始连接上服务器获取直播间列表也好还是发送数据创建直播间也好 成功失败都会从服务器推送到onmessage()这里 所以我们需要区别对待
const get_cmd = result.cmd.split(".");//分割成数组 形式 cast.create.success 判断是否是success 如果不是则弹出
if(get_cmd.length!=3) return;
const [module,command,status]=get_cmd;
if(status!=='success'){
alert(result.data);
return;
}
switch(command){
case "create":
alert("房间创建成功!");
break;
case "top":
// alert("获取列表成功!");
// console.log(result.data);
//我们将房间列表展示到前端界面
const castlist = document.getElementById("castlist");
castlist.innerHTML='';//这里要记得清空ul里面的数据 因为js里面有定时任务1.5秒去请求一次ws后端接口 更新直播列表到ul当中去
result.data.forEach((item)=>{
const li=document.createElement("li")
li.innerHTML=item.wTitle+" "+item.wTime;
castlist.appendChild(li);
});
break;
}
}
}
}
function $(id){ //原生js写法 定义$
return document.getElementById(id);
}
function create(){ //创建直播间
const username = $("userName").value;
const title = $("castTitle").value;
const req = {cmd:'cast.create',data:{title,username},ext:[]};//json格式的必须按照这种写法 {cmd:'',data:'',ext:[]} 详情看手册:消息控制器
ws.send(JSON.stringify(req));//JSON.stringfy() 转变成json字符串
}
function getCast(){ //获取直播列表
const req = {cmd:'cast.top',data:{},ext:{}}
ws.send(JSON.stringify(req));//请求cast.top获取直播间列表数据
}
</script>
<body>
<div style="width:30%;float:left">
<div>
<p>输入直播间标题:
<input type="text" id="castTitle"></p>
<p> 用户名:
<input type="text" id="userName"></p>
<p><input type="button" value="创建" onclick="create()" /></p>
</div>
<div>
<p><b>当前直播间列表</b></p>
<ul id="castlist">
</ul>
</div>
</div>
<div style="width:69%;float: right;">
</div>
</body>
</html>
后端代码: a.消息控制器
<?php
/**
* Created by PhpStorm.
* User: 胡军
* Date: 2019/10/27
* Time: 14:33
*/
namespace App\Ws;
use App\Models\Webcast;
use Swoft\WebSocket\Server\Annotation\Mapping\MessageMapping;
use Swoft\WebSocket\Server\Annotation\Mapping\WsController;
use Swoft\WebSocket\Server\Message\Request;
/**
* Class CastController
* @package App\Ws
* @WsController(prefix="cast")
* 解释:详细注释参考TestController
*/
class CastController
{
/**
* @MessageMapping(command="create")
* @throws \Swoft\Db\Exception\DbException
*/
public function createCast(Request $request){
// var_dump($request->getMessage()->getData());
['username'=>$username,'title'=>$title] = $request->getMessage()->getData();//获取发送过来的json参数
//创建直播间数据入库
$wc = new Webcast();
$wc->setWUser($username);
$wc->setWTitle($title);
$wc->setWTime(date('Y-m-d h:i:s'));
if($wc->save()){
/*
* Request当中的getFd()可以获取访问者的唯一标识 但是Message()类当中没有该功能的方法 所以我们以后统一使用Request()类
* 因为我们再定义模块的时候规定了messageParser=JsonParser::class 是json 所以这里必须返回json格式的数据 json_encode(['a'=>1,'b'=>2])也可以 没有说固定的必须是cmd data ext参数
* 之前我们使用的是Session::mustGet()->push("this is msg from sex");发送消息 但是以后我们统一使用server()系统函数来发送
* */
server()->sendTo($request->getFd(),json_encode([
"cmd"=>'cast.create.success',
"data"=>$wc->getId(),
"ext"=>[]
]));
//直播间列表数据 只要是有更新 那么我们就推送给全部的用户最新的直播间列表数据来展示 从而避免了使用在前端轮训的形式请求后台接口这种耗费资源的做法
sgo(function(){
$topList = Webcast::take(10)->orderByDesc("id")->get();//获取前10条
server()->sendToAll(json_encode([
"cmd"=>"cast.top.success",
"data"=>$topList,
"ext"=>[]
]));
});
}else{
server()->sendTo($request->getFd(),json_encode([
"cmd"=>'cast.create.error',
"data"=>"入库失败!",
"ext"=>[]
]));
}
}
/**
* @param Request $request
* @MessageMapping(command="top")
*/
public function topCast(Request $request){
$topList = Webcast::take(10)->orderByDesc("id")->get();//获取前10条
server()->sendTo($request->getFd(),json_encode([
"cmd"=>"cast.top.success",
"data"=>$topList,
"ext"=>[]
]));
}
}
b.定义模块
<?php
/**
* Created by PhpStorm.
* User: 胡军
* Date: 2019/10/27
* Time: 14:31
*/
namespace App\Ws;
use Swoft\WebSocket\Server\Annotation\Mapping\WsModule;
use Swoft\WebSocket\Server\MessageParser\JsonParser;
/**
* Class CastModule
* @package App\Ws
* @WsModule(path="/cast",controllers={
CastController::class
* },messageParser=JsonParser::class)
* 解释:这里的写法和TestWs当中一样 详情看里面的注释
*/
class CastModule
{
}
再来讲讲如何实现ws服务器推送内容到指定用户功能的实现:
上图:
代码我没写有思路就够了
当然你在ws.send()发送json格式的时候完全可以将当前用户的id传递过去
然后将该用户连接websocket服务器的唯一标识和用户id一一对应记录到mysql或者redis当中 假设该用户有一条新的用户消息入库
根据入库的用户的id去redis或者mysql表当中获取连接的唯一标识fd 通过websocket发送到指定的链接到ws服务器的用户那里去
当该用户关闭链接的时候再去触发另一个接口 将redis或者mysql当中的对应关系记录删除掉
这样就做到了后端服务器推送消息到执行前端用户的功能!const req = {cmd:'cast.create',data:{title,username,uid},ext:[]};
这种形式也避免了前端ajax轮询请求或者js定时任务请求对服务器照成的资源浪费