# WebSocket路由
接上一节,Websocket配置。
~~~
esd:
port:
websocket:
host: 0.0.0.0
port: 8082
open_websocket_protocol: true
sock_type: 1
pack_tool: 'ESD\Plugins\Pack\PackTool\WebsocketPack'
~~~
> pack\_tool: 'ESD\\Plugins\\Pack\\PackTool\\WebsocketPack' 指向了包工具类
~~~
<?php
/**
* ESD framework
* @author tmtbe <896369042@qq.com>
* @author bearlod <565364226@qq.com>
*/
namespace ESD\Plugins\Pack\PackTool;
use ESD\Core\Plugins\Logger\GetLogger;
use ESD\Core\Server\Config\PortConfig;
use ESD\Core\Server\Server;
use ESD\Plugins\Pack\ClientData;
use ESD\Plugins\Pack\PackTool\AbstractPack;
/**
* Class WebsocketPack
* @package ESD\Plugins\Pack\PackTool
*/
class WebsocketPack extends AbstractPack
{
use GetLogger;
/**
* Packet pack
*
* @param $data
* @param PortConfig $portConfig
* @param string|null $topic
* @return false|string
*/
public function pack($data, PortConfig $portConfig, ?string $topic = null)
{
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
/**
* Packet unpack
*
* @param int $fd
* @param string $data
* @param PortConfig $portConfig
* @return ClientData
* @throws \ESD\Core\Plugins\Config\ConfigException
* @throws \Exception
*/
public function unPack(int $fd, string $data, PortConfig $portConfig): ?ClientData
{
$value = json_decode($data, true);
if (empty($value)) {
$this->warn('Packet unpack failed');
return null;
}
return new ClientData($fd, $portConfig->getBaseType(), $value['p'], $value);
}
/**
* Packet encode
* @param string $buffer
*/
public function encode(string $buffer)
{
}
/**
* Packet decode
*
* @param string $buffer
*/
public function decode(string $buffer)
{
}
/**
* Change port config
*
* @param PortConfig $portConfig
* @return bool
*/
public static function changePortConfig(PortConfig $portConfig): bool
{
return true;
}
}
~~~
`unPack`方法处理,服务端收到的Websocket数据。
~~~
$clientData = new ClientData($fd, $portConfig->getBaseType(), $value['p'], $value);
~~~
> 第1个参数,Websocket连接标志,Swoole提供。
>
> 第2个参数 `$portConfig->getBaseType()` 的值是 `ws`,
>
> 第3个参数 `$value['p']`,指向AOP解析过后的路由路径,需要与客户端上传的JSON数据的key对应。如客户端上传的数据为 `{"p": "join", "room": "101", "nickname":"zhangsan"}`,对应的路由路径为 `/join`。【如果你有强迫症,不想用参数 `p`,改为喜欢的值即可。】
>
> 所以,还需要路由信息。
## 通过AOP新增动态路由
~~~
<?php
namespace App\Controller;
use ESD\Go\GoController;
use ESD\Plugins\EasyRoute\Annotation\RequestMapping;
use ESD\Plugins\EasyRoute\Annotation\WsController;
use ESD\Plugins\Pack\GetBoostSend;
use ESD\Plugins\Redis\GetRedis;
/**
* @WsController()
* Class WebSocket
* @package app\Controller
*/
class WebSocketController extends GoController
{
use GetRedis;
use GetBoostSend;
/**
* @RequestMapping("join")
* @throws \ESD\Plugins\Redis\RedisException
*/
public function actionJoin()
{
$fd = $this->clientData->getFd();
$data = $this->clientData->getData();
//检验数据唯一性略。房间号要存在,昵称不能重复。
//房间号
$room = $data['room'];
//昵称
$nickname = $data['nickname'];
//客人数据保存房间客户信息
$roomCustomerKey = sprintf("room_customers_%s", $room);
$this->redis()->sAdd($roomCustomerKey, $nickname);
//客人与fd对应关系
$customerFdKey = sprintf("customer_fd_%s", $nickname);
$this->redis()->set($customerFdKey, $fd);
}
/**
* @RequestMapping("send_to_friend")
* @throws \ESD\Plugins\Redis\RedisException
*/
public function actionSendToFriend()
{
$fd = $this->clientData->getFd();
$data = $this->clientData->getData();
//检验数据唯一性略。房间号要存在,昵称不能重复。
//房间号
$room = $data['room'];
//昵称
$friendNickname = $data['friend_nickname'];
//消息
$message = $data['message'];
//客人是否在房间内
$roomCustomerKey = sprintf("room_customers_%s", $room);
$exist = $this->redis()->sIsMember($roomCustomerKey, $friendNickname);
if (!$exist) {
return false;
}
//客人与fd对应关系
$friendFdKey = sprintf("customer_fd_%s", $friendNickname);
$friendFd = $this->redis()->get($friendFdKey);
$sendData = [
'p' => 'receive',
'data' => [
'message' => $message
]
];
$this->autoBoostSend($friendFd, $sendData);
}
}
~~~
#### 1.类注释 @WsController() 、方法注释 @RequestMapping() 必须。
#### 2.类名定义、方法名无要求。RequestMapping的地址,要与上述的
~~~
$clientData = new ClientData($fd, $portConfig->getBaseType(), $value['p'], $value);
~~~
的第3个参数一致,即客户端上传的JSON数据的 `p` 对应的值保持一致。
#### 3\. `ESD Framework` 启动时,经过 Annotations Scan,则会增加一条路由。
~~~
8082:WS /join -> App\Controller\WebSocketController::actionJoin
8082:WS /send_to_friend -> App\Controller\WebSocketController::actionSendToFriend
~~~
#### 测试用例场景
启动两个浏览器,模拟两个客户,一个昵称为 `zhangsan`,一个昵称为 `lisi`。`lisi` 可以给 `zhangsan` 发消息。
客户端例子:
~~~
<script>
// 连接服务端
const wsUri = "ws://192.168.108.131:8082";
function connect() {
// 创建websocket
ws = new WebSocket(wsUri);
// 当socket连接打开时,输入用户名
ws.onopen = onopen;
// 当有消息时根据消息类型显示不同信息
ws.onmessage = onmessage;
ws.onclose = function () {
console.log("连接关闭,定时重连");
connect();
};
ws.onerror = function () {
console.log("出现错误");
};
}
function onopen() {
}
// 服务端发来消息时
function onmessage(e) {
var data = e.data;
console.log(data);
console.log(JSON.parse(data));
}
connect();
</script>
~~~
**`zhangsan`端打开控制台:**
~~~
ws.send(JSON.stringify({"p": "join", "room": "101", "nickname":"zhangsan"}))
~~~
发送房间号 `room`,昵称 `zhangsan`,让服务端发现并保存连接。
**`lisi`端打开控制台:**
~~~
ws.send(JSON.stringify({"p": "join", "room": "101", "nickname":"lisi"}))
~~~
发送房间号 `room`,昵称 `lisi`,让服务端发现并保存连接。
**`lisi`端给张三发送消息:**
~~~
ws.send(JSON.stringify({"p": "send_to_friend", "room": "101", "friend_nickname":"zhangsan", "message":"hi"}))
~~~
**`zhangsan` 端控制台即可收到 `lisi` 端发送的消息:**
~~~
{"p":"receive","data":{"message":"hi"}}
~~~
收到的数据是个JSON字符串,用 `JSON.parse` 即可解析成 JSON对象。
同理:`zhangan` 端也可以给 `lisi` 发送消息。
- 1 介绍
- 2 安装
- 2.1 环境
- 2.2 安装
- 3 配置
- 3.1 Server配置
- 3.2 端口配置
- 3.3 项目结构
- 3.4 内核优化
- 4 服务
- 4.1 HTTP服务
- 4.1.1 路由
- 4.1.1.1 静态路由
- 4.1.1.2 路由定义
- 4.1.1.3 路由方法
- 4.1.1.4 路由分组
- 4.1.1.5 资源路由
- 4.1.1.6 端口作用域
- 4.1.1.7 异常处理
- 4.1.1.8 跨域请求
- 4.1.1.9 路由缓存
- 4.1.2 控制器
- 4.1.2.1 控制器初始化
- 4.1.2.2 前置后置操作
- 4.1.2.3 跳转与重定向
- 4.1.2.4 异常处理
- 4.1.3 请求
- 4.1.3.1 请求对象
- 4.1.3.2 请求信息
- 4.1.3.3 REQUEST消息
- 4.1.3.4 RESPONSE消息
- 4.1.3.5 STREAM消息
- 4.1.3.6 URI信息
- 4.1.3.7 处理上传文件
- 4.1.3.8 验证器
- 4.2 Websocket服务
- 4.2.1 Websocket配置
- 4.2.2 Websocket路由
- 4.3 TCP服务
- 4.3.1 TCP配置
- 4.3.2 TCP路由
- 4.3.3 协程处理案例
- 5 插件
- 5.15 Yii-PDO插件
- 5.15.1 PDO 连接MySQL Mariadb
- 5.15.2 PDO连接PostgreSQL
- 5.15.3 PDO连接GreenPlum
- 5.15.4 PDO连接Oracle
- 5.15.5 PDO连接Cubrid
- 5.15.6 PDO连接SQL Server
- 6 概念
- 7. Yii- I18N国际化
- 8. 模型 Yii-Model
- 8.1 快速创建模型
- 8.2 快速创建多个模型
- 8.3 核心验证器 Core Validators
- 9. 配合数据库工作
- 9.1.数据库访问对象 Database Access Objects
- 9.2 查询构造器 Query Builder
- 9.3 活动记录 Active Record