- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
导读
Hyperf 对 Swoole 的 WebSocket Server 进行了完整封装,基于 hyperf/websocket-server 组件即可快速构建 WebSocket 应用,并与 Hyperf 的依赖注入、路由、中间件、协程上下文等能力无缝集成。本文将以官方文档 WebSocket server 为核心脉络,完整讲解从安装、服务配置、路由与中间件绑定,到控制器编写、连接上下文、跨 Worker 主动推送以及"WebSocket 中处理 HTTP 请求"等进阶玩法的全过程,并结合仓库源码剖析握手校验、消息分发、fd 收集器等底层实现,帮助你写出可直接上线的 WebSocket 服务。
安装组件
WebSocket Server 是 Hyperf 的独立组件,通过 Composer 安装:
composer require hyperf/websocket-server从组件 composer.json 可以看到,该组件要求 PHP>= 8.2,依赖hyperf/contract、hyperf/http-server、hyperf/context、hyperf/exception-handler、hyperf/coordinator等 Hyperf 核心组件,并遵循 PSR-4 规范将Hyperf\WebSocketServer\命名空间映射到src/目录。组件通过 ConfigProvider.php 自动注册了两个监听器:Listener\InitSenderListener(初始化 Sender 的 WorkerId)与Listener\OnPipeMessageListener(处理跨 Worker 的管道消息),安装后无需额外手动注册即可生效。
配置 WebSocket Server
在config/autoload/server.php中新增一个servers配置项,即可声明一个 WebSocket 服务:
<?php return [ 'servers' => [ [ 'name' => 'ws', 'type' => Server::SERVER_WEBSOCKET, 'host' => '0.0.0.0', 'port' => 9502, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'], Event::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'], Event::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'], ], ], ], ];各配置项含义如下:
| 配置项 | 说明 |
|---|---|
name | 服务名称,路由与中间件配置都会以它为 key 关联到对应服务 |
type | 服务类型,WebSocket 使用Server::SERVER_WEBSOCKET |
host/port | 监听地址与端口,示例中 WebSocket 服务监听9502 |
sock_type | Socket 类型,SWOOLE_SOCK_TCP表示 TCP 协议 |
callbacks | 三个关键事件的回调:onHandShake(握手)、onMessage(消息)、onClose(关闭) |
这三个回调全部指向Hyperf\WebSocketServer\Server类的对应方法。从源码 Server.php 可见,Server实现了OnHandShakeInterface、OnCloseInterface、OnMessageInterface,并通过initCoreMiddleware()读取middlewares.{serverName}与exceptions.handler.{serverName}配置,默认异常处理器为WebSocketExceptionHandler。
配置路由
目前 WebSocket 服务仅支持配置文件方式定义路由,注解方式即将支持。
在config/routes.php中,使用Router::addServer()将路由注册到名为ws的服务上(ws即config/autoload/server.php中 WebSocket Server 的name):
<?php Router::addServer('ws', function () { Router::get('/', 'App\Controller\WebSocketController'); });与 HTTP 路由不同,WebSocket 路由最终指向的是一个控制器类而非具体方法。底层 CoreMiddleware.php 在handleFound()中会通过prepareHandler()解析出控制器类,将其作为class属性写入响应对象;如果路由不存在或容器中无对应控制器,会抛出WebSocketHandShakeException。握手成功后,该控制器类会被记录进 fd 收集器(见下文),后续的onMessage、onClose事件都会实例化同一个控制器来分发。
配置中间件
WebSocket 服务同样支持中间件,在config/autoload/middlewares.php中以服务名ws为 key 配置:
<?php return [ 'ws' => [ yourMiddleware::class ], ];这些中间件会在握手阶段(onHandShake)被执行。Server.php 的握手流程中,coreMiddleware->dispatch()完成路由分发后,会将全局中间件($this->middlewares)与通过MiddlewareManager::get()获取的路由级中间件合并,再交给HttpDispatcher统一调度——因此 WebSocket 的鉴权、限流等中间件逻辑与 HTTP 服务共用同一套中间件机制,用法完全一致。
创建控制器
创建一个同时实现OnMessageInterface、OnOpenInterface、OnCloseInterface的控制器:
<?php declare(strict_types=1); namespace App\Controller; use Hyperf\Contract\OnCloseInterface; use Hyperf\Contract\OnMessageInterface; use Hyperf\Contract\OnOpenInterface; use Swoole\Http\Request; use Swoole\Server; use Swoole\Websocket\Frame; use Swoole\WebSocket\Server as WebSocketServer; class WebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface { public function onMessage($server, Frame $frame): void { $server->push($frame->fd, 'Recv: ' . $frame->data); } public function onClose($server, int $fd, int $reactorId): void { var_dump('closed'); } public function onOpen($server, Request $request): void { $server->push($request->fd, 'Opened'); } }三个回调接口分别对应 WebSocket 生命周期的三个阶段:
onOpen:握手成功、连接建立后触发,通常在此做初始化(如记录在线用户);onMessage:收到客户端消息时触发,$frame->data为消息内容,$frame->fd为连接句柄;onClose:连接关闭时触发。
在非协程风格(异步风格)的 Swoole Server 下,Server.php 会在握手成功后通过defer()延迟执行onOpen;在协程风格服务(CoroutineServer、SwowServer)下则通过wait()包裹,并在子协程中预先设置好Context::FD,保证onOpen也能正确读取连接上下文。另外,onMessage 与 onClose 都会先从FdCollector中取出握手阶段记录的控制器类再实例化调用,若 fd 不存在会直接返回并记录 warning 日志。
启动服务
执行启动命令,即可看到 WebSocket Server 成功监听 9502 端口:
$ php bin/hyperf.php start [INFO] Worker#0 started. [INFO] WebSocket Server listening at 0.0.0.0:9502 [INFO] HTTP Server listening at 0.0.0.0:9501!> 当 HTTP Server(9501)与 WebSocket Server(9502)同时监听时,WebSocket 客户端通过两个端口都可以连接,即连接ws://0.0.0.0:9501与ws://0.0.0.0:9502均有效。
原因是Swoole\WebSocket\Server继承自Swoole\Http\Server,天然兼容 HTTP 协议,因此可以用 HTTP 方式完成所有 WebSocket 推送。如果希望 HTTP 服务不再受理 WebSocket 协议升级,可以在config/autoload/server.php中给http服务加上open_websocket_protocol配置并设为false:
<?php return [ // 无关配置已省略 'servers' => [ [ 'name' => 'http', 'type' => Server::SERVER_HTTP, 'host' => '0.0.0.0', 'port' => 9501, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'], ], 'settings' => [ 'open_websocket_protocol' => false, ] ], ] ];源码视角:握手是如何完成的
关于握手,Security.php 封装了完整的协议校验逻辑:isInvalidSecurityKey()用正则#^[+/0-9A-Za-z]{21}[AQgw]==$#校验客户端传来的sec-websocket-key并验证其 base64 解码后长度为 16 字节;handshakeHeaders()则会生成Upgrade: websocket、Connection: Upgrade、Sec-WebSocket-Accept(通过sha1(key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11)签名)等响应头。CoreMiddleware 将响应状态设置为101,即完成了标准的 WebSocket 协议升级。
连接上下文(Connected Context)
WebSocket 的onOpen、onMessage、onClose回调不会在同一个协程中触发,因此它们之间无法直接使用协程上下文存储的数据。为此,WebSocket Server 组件提供了Connected Context(连接上下文),其 API 与协程上下文一致,但数据以fd为维度隔离,天然解决"每个连接各存一份数据"的需求。
<?php declare(strict_types=1); namespace App\Controller; use Hyperf\Contract\OnMessageInterface; use Hyperf\Contract\OnOpenInterface; use Hyperf\WebSocketServer\Context; use Swoole\Http\Request; use Swoole\Websocket\Frame; use Swoole\WebSocket\Server as WebSocketServer; class WebSocketController implements OnMessageInterface, OnOpenInterface { public function onMessage($server, Frame $frame): void { $server->push($frame->fd, 'Username: ' . Context::get('username')); } public function onOpen($server, Request $request): void { Context::set('username', $request->cookie['username']); } }从源码 Context.php 可以看到其实现原理:set()内部通过CoContext::get(Context::FD)取得当前连接的 fd,再将数据存入"{fd}.{key}"形式的键中,get()/has()也按相同规则读取,从而实现按连接隔离的存储。该上下文还提供destroy()、release()(连接关闭时清理)、copy()(将某个连接的上下文复制到当前连接)、override()、getOrSet()等能力。在 Server.php 的onClose中,通过defer()延迟执行FdCollector::del($fd)与Context::release($fd),确保连接关闭后相关数据被及时回收。
多 WebSocket Server 配置(Nginx 负载均衡)
当需要部署多个 WebSocket Server 实例时,可以通过 Nginx 的upstream做反向代理与负载均衡。官方示例配置如下:
# /etc/nginx/conf.d/ng_socketio.conf # multiple ws server upstream io_nodes { server ws1:9502; server ws2:9502; } server { listen 9502; # server_name your.socket.io; location / { proxy_set_header Upgrade "websocket"; proxy_set_header Connection "upgrade"; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header Host $host; # proxy_http_version 1.1; # Forward to multiple ws server proxy_pass http://io_nodes; } }注意 Nginx 代理 WebSocket 的关键在于必须设置Upgrade与Connection两个请求头(如上所示),否则协议升级无法完成。多个ws节点通过upstream统一对外,客户端只需连接 Nginx 的 9502 端口。
Sender:跨 Worker 主动推送与断开
WebSocket 连接归属于具体的 Worker 进程,当你想在HTTP 服务中主动推送消息或断开连接时,直接调用 Swoole 原生的push()是行不通的(目标 fd 可能不在当前 Worker 内)。此时应使用组件提供的Hyperf\WebSocketServer\Sender。
Sender的工作机制:先检查fd是否归属于当前 Worker,若属于则直接发送;否则通过PipeMessage将消息广播给其他所有 Worker,由持有该 fd 的 Worker 完成实际发送。Sender.php 的__call()正是这一逻辑的入口——proxy()直接发送失败后,会调用sendPipeMessage()向其余每个 Worker 发送SenderPipeMessage;另一端由 OnPipeMessageListener 监听OnPipeMessage事件并再次调用proxy()完成投递。
Sender支持两个方法:push(推送数据)与disconnect(断开连接)。典型用法如下:
<?php declare(strict_types=1); namespace App\Controller; use Hyperf\Di\Annotation\Inject; use Hyperf\HttpServer\Annotation\AutoController; use Hyperf\WebSocketServer\Sender; use function Hyperf\Coroutine\go; #[AutoController] class ServerController { #[Inject] protected Sender $sender; public function close(int $fd) { go(function () use ($fd) { sleep(1); $this->sender->disconnect($fd); }); return ''; } public function send(int $fd) { $this->sender->push($fd, 'Hello World.'); return ''; } }除此之外,Sender还提供pushFrame()用于推送FrameInterface数据帧(可指定 opcode 与 finish 标志),以及check($fd)方法(通过connection_info()判断该连接是否处于WEBSOCKET_STATUS_ACTIVE状态)判断连接是否仍然活跃。需要留意的是,在协程风格服务(server.type为CoroutineServer或SwowServer)下,Sender直接基于保存在responses中的连接对象发送,无需跨进程通信。InitSenderListener会在 Worker 启动时调用setWorkerId()记录当前 WorkerId,这是判断"fd 是否归属当前 Worker"的基础。
在 WebSocket Server 中处理 HTTP 请求
除了通过端口分离 HTTP 与 WebSocket 服务外,还可以让 WebSocket 服务同时处理 HTTP 请求。由于server.servers.*.callbacks中的配置项都是单例,需要先在config/autoload/dependencies.php中声明一个新的单例:
<?php return [ 'HttpServer' => Hyperf\HttpServer\Server::class, ];然后修改 WebSocket 服务的callbacks配置,在原有三个回调基础上追加Event::ON_REQUEST(以下省略无关配置):
<?php declare(strict_types=1); use Hyperf\Server\Event; use Hyperf\Server\Server; return [ 'mode' => SWOOLE_BASE, 'servers' => [ [ 'name' => 'ws', 'type' => Server::SERVER_WEBSOCKET, 'host' => '0.0.0.0', 'port' => 9502, 'sock_type' => SWOOLE_SOCK_TCP, 'callbacks' => [ Event::ON_REQUEST => ['HttpServer', 'onRequest'], Event::ON_HAND_SHAKE => [Hyperf\WebSocketServer\Server::class, 'onHandShake'], Event::ON_MESSAGE => [Hyperf\WebSocketServer\Server::class, 'onMessage'], Event::ON_CLOSE => [Hyperf\WebSocketServer\Server::class, 'onClose'], ], ], ], ];配置完成后,即可在ws服务中直接添加 HTTP 路由。这种模式适用于"同一端口同时提供 REST API 与 WebSocket 长连接"的场景,例如聊天室的鉴权接口与实时消息通道共用 9502 端口。由于Swoole\WebSocket\Server本身继承自Swoole\Http\Server,这在协议层面是完全支持的。
总结
至此,你已经完整掌握了 Hyperf 中 WebSocket Server 的核心用法:
- 服务声明:在
config/autoload/server.php中配置SERVER_WEBSOCKET类型服务并绑定onHandShake/onMessage/onClose回调; - 路由与中间件:通过
Router::addServer('ws', ...)注册控制器路由,通过middlewares.php的ws键绑定中间件,握手阶段即完成路由分发与中间件调度; - 生命周期控制:控制器实现
OnOpenInterface/OnMessageInterface/OnCloseInterface三接口,配合Connected Context按连接隔离数据; - 主动推送:利用
Sender在 HTTP 层跨 Worker 完成push/disconnect,底层通过 PipeMessage 实现进程间协作; - 扩展玩法:通过
open_websocket_protocol关闭 HTTP 端的 WebSocket 升级,通过追加ON_REQUEST回调让 WebSocket 服务同时承载 HTTP 请求。
如需进一步验证各环节行为,可以查阅组件的单元测试(ServerTest.php、ContextTest.php、SenderTest.php),并结合 websocket-client 文档 编写客户端完成端到端联调。
- 后端
- 微服务
【免费下载链接】hyperf
🚀 A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.
相关推荐
Java-WebSocket完全指南:从零构建高性能WebSocket客户端与服务器
Java WebSocket完全指南:从零构建高性能WebSocket客户端与服务器 引言:为什么选择Java WebSocket? 你是否正在寻找一个轻量级、
后端WebSocket网络通信tchMaterial-parser:一键把智慧教育平台在线电子课本下载成本地PDF
tchMaterial parser:一键把智慧教育平台在线电子课本下载成本地PDF 把国家中小学智慧教育平台的教材预览页网址粘进 tchMaterial pa
后端微服务终极VibeVoice实时语音生成指南:从零搭建WebSocket服务
终极VibeVoice实时语音生成指南:从零搭建WebSocket服务 VibeVoice是微软开源的前沿语音AI项目,其 实时语音生成 功能能够实现约300毫
语音音频人工智能大模型模型推理服务微调
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考