作者:顾世嵐 | 来源:互联网 | 2023-07-05 12:14
步骤1 修改配置文件MAIN_SERVER.SERVER_TYPE
为EASYSWOOLE_WEB_SOCKET_SERVER
如dev.php
use EasySwoole\Log\LoggerInterface;return ['SERVER_NAME'=>"EasySwoole",'MAIN_SERVER'=>['LISTEN_ADDRESS'=>'0.0.0.0','PORT'=>'19501',"SERVER_TYPE"=>EASYSWOOLE_WEB_SOCKET_SERVER, //可选为 EASYSWOOLE_SERVER EASYSWOOLE_WEB_SERVER EASYSWOOLE_WEB_SOCKET_SERRVER "SOCK_TYPE"=>SWOOLE_TCP,"RUN_MODEL"=>SWOOLE_PROCESS,"SETTING"=>['worker_num'=>8,'reload_async'=>true,'max_wait_time'=>3,'package_max_length'=>1024*1024*1024,'max_connection'=>150000,'socket_buffer_size'=>1024*1024*1024],],
];
步骤2 EasySwooleEvent中mainServerCreate事件进行回调注册:
{$cOnfig= new \EasySwoole\Socket\Config();$config->setType($config::WEB_SOCKET);$config->setParser(WebSocketParser::class);$dispatcher = new Dispatcher($config);$config->setOnExceptionHandler(function (\Swoole\Server $server, \Throwable $throwable, string $raw, WebSocket $client, Response $response) {$response->setMessage('system error!');$response->setStatus($response::STATUS_RESPONSE_AND_CLOSE);});// 自定义握手/*$websocketEvent = new WebSocketEvent();$register->set(EventRegister::onHandShake, function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) use ($websocketEvent) {$websocketEvent->onHandShake($request, $response);});*/$register->set($register::onMessage, function (\Swoole\Websocket\Server $server, \Swoole\Websocket\Frame $frame) use ($dispatcher) {$dispatcher->dispatch($server, $frame->data, $frame);});//注册服务时间$register->add(EventRegister::onOpen,[WebSocketEvents::class,'onOpen']);$register->add(EventRegister::onClose,[WebSocketEvents::class,'onClose']);}
步骤3 WebSocketEvents.php
use EasySwoole\Mysqli\Config as MysqlConfig;
use EasySwoole\RedisPool\Config as RedisConfig;
use EasySwoole\RedisPool\Redis;
use EasySwoole\FastCache\Cache;
use SebastianBergmann\CodeCoverage\Report\PHP;class WebSocketEvents {//监听ws连接事件public static function onOpen(\swoole_websocket_server $server, \swoole_http_request $request) {echo $request->fd . '链接成功' . PHP_EOL;//return true;}//监听ws关闭事件public static function onClose(\swoole_server $server, int $fd, int $reactorId) {//echo $reactorId . ' -- ' . $fd . ' websocket 关闭' . PHP_EOL;//return true;}/*** @param \Swoole\Http\Request $request* @param \Swoole\Http\Response $response* @return bool*/public function onHandShake(\Swoole\Http\Request $request, \Swoole\Http\Response $response) {/** 此处自定义握手规则 返回 false 时中止握手 */if (!$this->customHandShake($request, $response)) {$response->end();return false;}/** 此处是 RFC规范中的WebSocket握手验证过程 必须执行 否则无法正确握手 */if ($this->secWebsocketAccept($request, $response)) {$response->end();return true;}$response->end();return false;}/*** @param \Swoole\Http\Request $request* @param \Swoole\Http\Response $response* @return bool*/protected function customHandShake(\Swoole\Http\Request $request, \Swoole\Http\Response $response): bool {/*** 这里可以通过 http request 获取到相应的数据* 进行自定义验证后即可* (注) 浏览器中 Javascript 并不支持自定义握手请求头 只能选择别的方式 如get参数*/$headers = $request->header;$COOKIE = $request->COOKIE;// if (如果不满足我某些自定义的需求条件,返回false,握手失败) {// return false;// }return true;}/*** RFC规范中的WebSocket握手验证过程* 以下内容必须强制使用** @param \Swoole\Http\Request $request* @param \Swoole\Http\Response $response* @return bool*/protected function secWebsocketAccept(\Swoole\Http\Request $request, \Swoole\Http\Response $response): bool {// ws rfc 规范中约定的验证过程if (!isset($request->header['sec-websocket-key'])) {// 需要 Sec-WebSocket-Key 如果没有拒绝握手var_dump('shake fai1 3');return false;}if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])|| 16 !== strlen(base64_decode($request->header['sec-websocket-key']))) {//不接受握手var_dump('shake fai1 4');return false;}$key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));$headers = array('Upgrade' => 'websocket','Connection' => 'Upgrade','Sec-WebSocket-Accept' => $key,'Sec-WebSocket-Version' => '13','KeepAlive' => 'off',);if (isset($request->header['sec-websocket-protocol'])) {$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];}// 发送验证后的headerforeach ($headers as $key => $val) {$response->header($key, $val);}// 接受握手 还需要101状态码以切换状态$response->status(101);var_dump('shake success at fd :' . $request->fd);return true;}
}
步骤4 websocket解析器
use EasySwoole\Socket\Client\WebSocket;
use EasySwoole\Socket\Bean\Caller;
use EasySwoole\Socket\Bean\Response;/*** Class WebSocketParser** 此类是自定义的 websocket 消息解析器* 此处使用的设计是使用 json string 作为消息格式* 当客户端消息到达服务端时,会调用 decode 方法进行消息解析* 会将 websocket 消息 转成具体的 Class -> Action 调用 并且将参数注入** @package App\WebSocket*/
class WebSocketParser implements ParserInterface {/*** decode* @param string $raw 客户端原始消息* @param WebSocket $client WebSocket Client 对象* @return Caller Socket 调用对象*/public function decode($raw, $client): ?Caller {// new 调用者对象$caller = new Caller();// 解析 客户端原始消息$data = json_decode($raw, true);if (!is_array($data)) {echo "decode message error1111! \n";return null;}/*** 设置被调用的类 这里会将ws消息中的 class 参数解析为具体想访问的控制器* 如果更喜欢 event 方式 可以自定义 event 和具体的类的 map 即可* 注 目前 easyswoole 3.0.4 版本及以下 不支持直接传递 class string 可以通过这种方式*/$class = '\\App\\WebSocket\\WSController\\' . ucfirst($data['class'] ?? 'Index');$caller->setControllerClass($class);$action = $data['action'];$caller->setAction($action);// 检查是否存在args$args = isset($data['params']) && !empty($data['params']) ? $data['params'] : [];// 设置被调用的Args$caller->setArgs($args ?? []);return $caller;}/*** encode* @param Response $response Socket Response 对象* @param WebSocket $client WebSocket Client 对象* @return string 发送给客户端的消息*/public function encode(Response $response, $client): ?string {/*** 这里返回响应给客户端的信息* 这里应当只做统一的encode操作 具体的状态等应当由 Controller处理*/return $response->getMessage();}}
步骤五 新增Websoket 控制器 Index.php
App\WebSocket\WSController
/** ws控制器测试**/namespace App\WebSocket\WSController;use EasySwoole\EasySwoole\ServerManager;
use EasySwoole\Socket\AbstractInterface\Controller;class Index extends Controller {public function index() {$client = $this->caller()->getClient();$server = ServerManager::getInstance()->getSwooleServer();$post_info = $this->caller()->getArgs();if ($server->getClientInfo($client->getFd())) {$server->push($client->getFd(), json_encode(['data' => 'success']));}}public function index1(){var_dump('这里是index1');}}
步骤六 连接websoket 进行测试
前端html 代码如下:
注: 其中发送的数据 JSON字符串 {"class":"Index1","action":"index1","params":[]}
class 表示请求到对应的Websoket控制器
action 标识请求到对应的Websoket方法
params 为发送的数据,这里必须数组 若为其他,可能接收不到!
结果如图:
![](https://img2.php1.cn/3cdc5/3ce1/339/6a5328fbfede8466.jpeg)