wu / giorgio-socket
Laravel WebSocket server with Swoole and native PHP transports
Requires
- php: ^8.0.2
- laravel/framework: >=9.0
Requires (Dev)
None
Suggests
- ext-openssl: Required for direct TLS listeners
- ext-swoole: Preferred WebSocket transport on non-Windows systems
Provides
None
Conflicts
None
Replaces
None
README
Laravel WebSocket 服务,支持 Swoole 和 PHP 原生驱动
相关包
- Giorgio IM:Vue 3 + TypeScript 即时通讯组件
- Giorgio IM Socket:Laravel 即时通讯后端
- Giorgio Socket:Laravel WebSocket 底层服务
安装
环境要求:PHP 8.0.2+、Laravel 9+、Redis,直连 TLS 需要 OpenSSL
composer require wu/giorgio-socket
php artisan vendor:publish --provider="GiorgioSocket\Providers\SocketServiceProvider" --tag=socket-config
配置
SOCKET_HOST=127.0.0.1 SOCKET_PORT=9501 SOCKET_DRIVER=auto SOCKET_PREFIX=giorgio-socket:9501: SOCKET_PUSH_URL=http://127.0.0.1:9501/push SOCKET_PUSH_TOKEN=replace-with-a-long-random-secret
SOCKET_DRIVER:auto 自动选择,swoole 强制使用 Swoole,native 强制使用 PHP 原生驱动。Windows 原生驱动最多 60 个并发连接
鉴权
鉴权器写在 config/socket.php:
'authenticate' => App\Services\SocketAuthenticator::class,
未配置鉴权器时连接会被拒绝。返回用户 ID 表示通过,返回 null 表示拒绝,后续报文会再次鉴权
<?php namespace App\Services; use GiorgioSocket\Services\ConnectionTicket; class SocketAuthenticator { public function __invoke(array $packet, bool $established = false): int|string|null { if (! is_string($packet['token'] ?? null)) { return null; } $claims = app(ConnectionTicket::class)->claims($packet['token'], $established); $id = $claims['user_id'] ?? null; return is_int($id) || is_string($id) ? $id : null; } }
票据由应用认证接口签发,默认有效期 60 秒,可用 SOCKET_TICKET_TTL 修改,签发端和 Socket 服务必须使用同一个 APP_KEY
$ticket = app(\GiorgioSocket\Services\ConnectionTicket::class)->issue([ 'user_id' => $request->user()->getKey(), ]);
连接
启动服务:
php artisan socket:start
客户端发送首帧:
const ws = new WebSocket('ws://127.0.0.1:9501'); ws.onopen = () => ws.send(JSON.stringify({ type: 'connect', token: ticket })); ws.onmessage = ({ data }) => console.log(JSON.parse(data));
| 报文 | 作用 |
|---|---|
connect |
验证票据,返回 connected 和 user_id |
ping |
重新鉴权,返回 pong |
close |
关闭连接 |
message |
默认拒绝,业务消息通过应用 API 发送 |
连接参数:ping 间隔 25 秒,空闲阈值 90 秒,Swoole 检查间隔 30 秒,单帧上限 65536 字节,空闲阈值统计所有客户端帧,不限于心跳
推送
推送接口:POST /push,鉴权方式:Bearer 密钥
$client = app(config('socket.handlers.client')); $client->handle((object) [ 'to' => $userId, 'message' => ['type' => 'updated', 'id' => 123], ]);
to 填用户 ID,to: 'all' 发送给当前在线用户,适合公告、提醒和状态更新,管理员页面与权限由应用处理
消息发送到用户的所有在线连接,全部失败时写入 Redis 离线队列,默认每个用户最多 100 条,保留 7 天
使用 Laravel 队列时:
GiorgioSocket\Events\SocketEvent::dispatch($userId, $payload);
异步队列需要运行:
php artisan queue:work --queue=socket-listener
持久事件
需要断线补取时发布迁移:
php artisan vendor:publish --provider="GiorgioSocket\Providers\SocketServiceProvider" --tag=socket-migrations
php artisan migrate
默认表:socket_events,表名配置:SOCKET_EVENTS_TABLE,事件应和业务数据放在同一个事务中
use GiorgioSocket\Services\EventStore; app(EventStore::class)->append([$userId], 'updated', ['id' => 123]); $page = app(EventStore::class)->after($userId, $cursor);
发布事件:
php artisan socket:events php artisan socket:events --once
失败事件会保留,客户端按事件 ID 去重,清理已发布事件:
php artisan socket:prune-events php artisan socket:prune-events --hours=24
清理命令放进 Laravel Scheduler 或 cron,未发布事件不会被清理
TLS
公网使用 WSS 和 HTTPS,/push 只开放给可信服务
SOCKET_TLS_ENABLED=true SOCKET_TLS_CERT_FILE=/absolute/path/fullchain.pem SOCKET_TLS_KEY_FILE=/absolute/path/privkey.pem SOCKET_PUSH_URL=https://socket-internal.example.com/push # SOCKET_PUSH_CA_FILE=/absolute/path/ca.pem
也可以让 Socket 监听回环地址,由反向代理终止 TLS。服务端只启用 TLS 1.2 和 TLS 1.3,推送请求会校验证书并拒绝重定向
扩展
presence 是可选在线回调,连接建立和每次心跳传入 true,最后一个设备断开传入 false,适合更新在线状态和最后活跃时间。回调会重复触发,广播状态变化前请自行判断状态是否改变
handlers 可替换默认处理器:
open:连接建立message:客户端报文close:连接关闭client:服务端推送
处理器接口在 src/Services/Handlers/Interfaces/,由 Laravel 容器创建
修改代码、配置或密钥后重启 socket:start 和 socket:events