成果如下![在这里插入图片描述](https://img-blog.csdnimg.cn/de07bb3cb67d4ad4bc8a343af087c7a4.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAd2VpeGluXzQ1ODA2MTAy,size_8,color_FFFFFF,t_70,g_se,x_16
环境搭建
1. tp5.0安装workerman
composer require topthink/think-worke
注:windows 环境下需要额外安装 workerman-for-win
composer require workerman/workerman-for-win
2.在项目根目录下新建 server.php
红色圈出的 是项目下真正用来处理请求的Worker 类
以下代码均为核心代码
Worker类的基本定义
use think\worker\Server;
class Worker extends Server
{
// 建立的连接地址
protected $socket = 'websocket://localhost:2345';
// 存储当前连接的用户集合,下面会详细介绍
public $clients=[];
//用户模型
public $m=null;
//消息记录的模型
public $message=null;
}
Workerman 提供的 Server类【think\worker\Server】 我们可以点进去查看,里面有一个init 方法,可以对其进行重写,并做一些初始化的操作,我仅在此对模型进行了初始化
public function init()
{
parent::init(); // TODO: Change the autogenerated stub
$this->m=new User();
$this->message=new Message();
}
Server类中的代码对 Socket 的各个事件都有其对应的回调方法
['onWorkerStart', 'onConnect', 'onMessage', 'onClose', 'onError', 'onBufferFull', 'onBufferDrain', 'onWorkerStop', 'onWorkerReload']
我仅使用了
- onMessage 即收到信息
- onClose 断开连接的回调
说说思路:
因为服务端都是通过onMessage来服务客户端,因此可以约定一个type参数用来表示当前操作类型,例如,type=”join” 意为首次建立连接并加入会话。需要记录当前的用户信息和连接对象( c o n n e c t i o n ) , 即 上 述 的 【 connection),即上述的【 connection),即上述的【ciients】建议用数组存储,键可以使用当前客户端的ip【此种方式会使得当前ip只能有一个用户】或者 ip+用户id以及其他唯一标识。 因此$clients看起来可能是如下形式
$clients=[
"127.0.0.1&12"=>{userInfo=>"当前链接的用户信息",key=>"127.0.0.1&12",...clients对象的其他属性}
]
/**
* 收到信息
* @param $connection
* @param $data Object 带过来的参数
*/
public function onMessage($connection, $data)
{
$data=json_decode($data);
if(empty($data->uid)) {
$connection->send("未携带uid");
}else{
//拼接请求的key
$data->key=$connection->getRemoteIp()."&".$data->uid;
//如果不存在于记录的连接数组 就将 添加进去
if(!array_key_exists($data->key,$this->clients)){
//查询出当前用户的昵称和头像
$temp=$this->m->field(["nickname","avatar","id"])->find($data->uid);
$temp["joinTime"]=date("Y-m-d H:i:s",time());
//将用户信息和key存在链接对象里
$connection->userInfo=$temp;
$connection->key=$data->key;
//保存当前用户链接的key
$this->clients[$data->key]=$connection;
//广播所有链接用户
$this->broadCast(['type'=>"join",'data'=>$temp,"total"=>sizeof($this->clients)]);
}else{
// 如果当前消息意为发送信息
if(!empty($data->type) && $data->type=="send"){
//将消息存储至数据库
$this->message->insert([
"createtime"=>time(),
"text"=>$data->data->text,
"images"=>$data->data->images,
"uid"=>$connection->userInfo->id
]);
//将消息广播至所有用户
$this->broadCast([
'type'=>"onMessage",
'data'=>$data->data,
"publisher"=>$connection->userInfo,
'publishTime'=>datetime(time())
]);
}
}
}
}
消息广播即遍历clients数组分别对其发送
/**
* 给当前所有的链接用户发送消息
* @param $data
* @param $exceptKey array 期望忽略的用户的key列表
*/
public function broadCast($data, array $exceptKey=[])
{
//遍历客户端数组
foreach ($this->clients as $k=>$v){
// 如果不是被忽略的对象 就给该用户发送数据
if(!in_array($k,$exceptKey)){
$v->send(json_encode($data));
}
}
}
当客户端断开连接,需要关闭当前链接,并告知所有用户
/**
* 当连接断开时触发的回调函数
* @param $connection
*/
public function onClose($connection)
{
$connection->close();
if(!empty($connection->key)){
unset($this->clients[$connection->key]);
$this->broadCast(['type'=>"leave",'data'=>$connection->userInfo,"total"=>sizeof($this->clients)]);
}
}
前端部分,将websocket对象放至pinia中了。
就建立连接发送消息,接收消息并渲染
function send(p = {}) {
//发送信息时携带uid
p["uid"] = store.uinfo.id
// p["uid"]=10;
store.websocket.send(JSON.stringify(p));
}
function connection() {
//如果当前链接对象是空或者还没链接
if (store.websocket == null || store.websocket.readyState != 1) {
store.websocket = new WebSocket("ws://localhost:2345");
console.log("进行重连");
store.websocket.onopen = function (evt) {
//立即将用户uid发过去 用来告知其他用户 有人加入
send();
online.value = true;
};
store.websocket.onclose = function (evt) {
console.log("关闭链接", evt);
online.value = false;
};
store.websocket.onmessage = function (evt) {
let data = JSON.parse(evt.data);
messageList.value.push(data);
};
store.websocket.onerror = function (evt) {
console.log("错误", evt);
};
} else {
online.value = true;
}
}
至此,基本功能就实现了。