多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 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` 发送消息。