Laravel中基于beyondcode/laravel-websockets实现简易WebSocket服务的问题
Laravel 实现兼容REST的简易WebSocket服务
需求概述
需要在Laravel中实现一个WebSocket服务,满足以下要求:
- WebSocket地址:
ws://{location}:3000/clock - 保留原有REST接口(如
http://{location}:3000/some-rest-thing)正常访问 - 功能逻辑:
- 维护所有连接的WebSocket客户端
- 收到客户端发送的
requestTime消息时,向所有在线客户端广播当前服务器毫秒级时间戳 - 功能与以下NodeJS示例完全一致:
const wsServer = new ws.WebSocketServer({ server: server, path: "/clock" }); wsServer.on('connection', socket => { socket.on('error', err => { console.error(err); }); socket.on('message', data => { if(data.toString() === "requestTime") { // broadcast time on requestTime event to all clients wsServer.clients.forEach(client => { if(client.readyState === ws.OPEN) { client.send((new Date()).getMilliseconds()); } }); } }); });
已尝试的问题
- 通过事件广播+API路由实现:客户端连接后立即断开,仅收到HTTP 200响应,无事件触发
- 使用
WebSocketsRouter闭包:抛出invalid websocket controller provided错误 - 实现
MessageComponentInterface的控制器:绑定后仍提示控制器无效 - 新项目复现问题:
- 按步骤安装配置后,用
php artisan serve --port=3000启动,WebSocket连接失败,仪表盘/laravel-websockets返回404 - 改用
php artisan websockets:serve --port=3000启动,仪表盘同样404,且无法访问REST接口
- 按步骤安装配置后,用
解决方案(基于beyondcode/laravel-websockets + 共享端口配置)
核心说明
beyondcode/laravel-websockets默认使用独立端口运行WebSocket服务,若要与REST接口共享同一端口,需将WebSocket服务挂载到Laravel的HTTP服务器上,而非单独启动websockets:serve。
步骤1:安装依赖(复用已完成的安装步骤)
确保已完成以下操作:
composer create-project laravel/laravel stresstest2 composer require beyondcode/laravel-websockets pusher/pusher-php-server php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config" php artisan migrate
步骤2:配置文件调整
- 修改
.env:BROADCAST_DRIVER=pusher QUEUE_CONNECTION=sync PUSHER_APP_ID=dummy PUSHER_APP_KEY=dummy PUSHER_APP_SECRET=dummy PUSHER_HOST=127.0.0.1 PUSHER_PORT=3000 PUSHER_SCHEME=http LARAVEL_WEBSOCKETS_ALLOW_ORIGINS=* - 修改
config/websockets.php:- 保留
apps数组中的dummy配置,确保allowed_origins设置为['*'] - 无需额外配置Pusher路由,我们将直接实现原生WebSocket逻辑
- 保留
步骤3:实现自定义WebSocket处理器
创建app/WebSockets/ClockWebSocketHandler.php:
<?php namespace App\WebSockets; use Ratchet\ConnectionInterface; use Ratchet\MessageComponentInterface; class ClockWebSocketHandler implements MessageComponentInterface { protected $clients; public function __construct() { $this->clients = new \SplObjectStorage; } public function onOpen(ConnectionInterface $conn) { // 记录新连接的客户端 $this->clients->attach($conn); echo "New client connected: {$conn->resourceId}\n"; } public function onMessage(ConnectionInterface $from, $msg) { // 处理requestTime请求 if (trim($msg) === 'requestTime') { $currentTime = (new \DateTime())->getTimestamp() * 1000; // 毫秒级时间戳 // 广播给所有在线客户端 foreach ($this->clients as $client) { if ($client->isOpen()) { $client->send((string)$currentTime); } } } } public function onClose(ConnectionInterface $conn) { // 移除断开连接的客户端 $this->clients->detach($conn); echo "Client disconnected: {$conn->resourceId}\n"; } public function onError(ConnectionInterface $conn, \Exception $e) { echo "Error: {$e->getMessage()}\n"; $conn->close(); } }
步骤4:挂载WebSocket到Laravel HTTP服务器
修改routes/console.php,添加自定义命令来启动集成了WebSocket的HTTP服务器:
<?php use App\WebSockets\ClockWebSocketHandler; use Illuminate\Contracts\Http\Kernel; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use React\EventLoop\Factory; use React\Socket\Server as SocketServer; use React\Http\Server as ReactHttpServer; use React\Http\Middleware\StreamingRequestMiddleware; Artisan::command('server:websocket', function () { $loop = Factory::create(); // 创建WebSocket处理实例,指定路径为/clock $webSocket = new WsServer(new ClockWebSocketHandler()); $webSocket->setProtocolVersion(13); $webSocket->enableKeepAlive($loop, 30); // 创建Laravel HTTP请求处理器 $kernel = app(Kernel::class); $httpHandler = function (\Psr\Http\Message\RequestInterface $request) use ($kernel) { $laravelRequest = \Illuminate\Http\Request::create( $request->getUri()->getPath(), $request->getMethod(), [], [], [], $request->getServerParams(), (string)$request->getBody() ); try { $response = $kernel->handle($laravelRequest); } catch (\Exception $e) { $response = response()->json(['error' => $e->getMessage()], 500); } $psrResponse = new \Nyholm\Psr7\Response( $response->getStatusCode(), $response->headers->all(), $response->getContent() ); $kernel->terminate($laravelRequest, $response); return $psrResponse; }; // 创建React HTTP服务器,同时处理HTTP和WebSocket $httpServer = new ReactHttpServer( $loop, new StreamingRequestMiddleware(), $httpHandler ); // 绑定端口并启动服务 $socket = new SocketServer('127.0.0.1:3000', $loop); $httpServer->listen($socket); // 将WebSocket服务挂载到/clock路径 $webSocketServer = new HttpServer($webSocket); $webSocketServer->listen($socket, ['path' => '/clock']); $this->info('Server running on http://127.0.0.1:3000'); $this->info('WebSocket running on ws://127.0.0.1:3000/clock'); $loop->run(); })->describe('Start a server that handles both HTTP requests and WebSocket connections');
步骤5:启动服务并测试
- 启动集成服务:
php artisan server:websocket - 测试REST接口:在
routes/api.php或routes/web.php定义路由(如Route::get('/some-rest-thing', fn() => response('REST works!'));),访问http://127.0.0.1:3000/some-rest-thing验证可用性 - 测试WebSocket:
使用浏览器控制台或WebSocket客户端连接ws://127.0.0.1:3000/clock,发送requestTime消息,即可收到服务器广播的毫秒级时间戳。
常见问题排查
- 客户端连接失败:检查端口是否被占用,确保
config/websockets.php中allowed_origins为['*'] - REST接口无响应:确认自定义命令中的Laravel请求处理逻辑正确,Kernel实例正常初始化
- 广播无消息:检查
ClockWebSocketHandler中的客户端管理逻辑,确保onOpen和onClose正确维护客户端列表
内容的提问来源于stack exchange,提问作者Nuubles
相关产品推荐
相关产品推荐

