You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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());
                      }
                  });
              }
          });
      });
      

已尝试的问题

  1. 通过事件广播+API路由实现:客户端连接后立即断开,仅收到HTTP 200响应,无事件触发
  2. 使用WebSocketsRouter闭包:抛出invalid websocket controller provided错误
  3. 实现MessageComponentInterface的控制器:绑定后仍提示控制器无效
  4. 新项目复现问题:
    • 按步骤安装配置后,用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:配置文件调整

  1. 修改.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=*
    
  2. 修改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:启动服务并测试

  1. 启动集成服务:
    php artisan server:websocket
    
  2. 测试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验证可用性
  3. 测试WebSocket:
    使用浏览器控制台或WebSocket客户端连接ws://127.0.0.1:3000/clock,发送requestTime消息,即可收到服务器广播的毫秒级时间戳。

常见问题排查

  • 客户端连接失败:检查端口是否被占用,确保config/websockets.php中allowed_origins为['*']
  • REST接口无响应:确认自定义命令中的Laravel请求处理逻辑正确,Kernel实例正常初始化
  • 广播无消息:检查ClockWebSocketHandler中的客户端管理逻辑,确保onOpen和onClose正确维护客户端列表

内容的提问来源于stack exchange,提问作者Nuubles

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 18:35:02