1. 项目概述
最近在重构一个需要实时通信功能的项目时,我选择了ThinkPHP8作为基础框架,同时整合Workerman来实现高性能的WebSocket服务。这种组合在实际项目中非常实用,既能利用ThinkPHP成熟的MVC架构,又能通过Workerman突破传统PHP在长连接应用中的性能瓶颈。
ThinkPHP8作为国内最流行的PHP框架之一,提供了优雅的代码结构和丰富的功能组件。而Workerman则是一个纯PHP开发的高性能Socket服务器框架,特别适合开发实时应用。将两者结合使用,可以充分发挥各自的优势,构建出既便于业务开发又具备高并发能力的应用系统。
2. 环境准备与基础配置
2.1 环境要求检查
在开始整合前,我们需要确保开发环境满足以下要求:
- PHP版本≥7.3(Workerman需要PHP7.0+,ThinkPHP8要求PHP7.1+)
- 已安装Composer依赖管理工具
- 服务器需要开启socket扩展(通常默认已安装)
- 建议使用Linux环境(Windows下Workerman性能会打折扣)
可以通过以下命令快速检查环境:
php -v composer -v php -m | grep sockets2.2 创建ThinkPHP8项目
使用Composer创建一个新的ThinkPHP8项目:
composer create-project topthink/think tp8-workerman-demo cd tp8-workerman-demo安装完成后,建议先测试基础框架是否正常运行:
php think run访问http://localhost:8000应该能看到ThinkPHP的欢迎页面。
2.3 安装Workerman
在项目根目录下通过Composer安装Workerman:
composer require workerman/workerman为了后续开发方便,我们还可以安装WebSocket支持:
composer require workerman/phpsocket.io3. Workerman服务整合
3.1 创建基础服务文件
在项目根目录创建server目录,然后新建websocket.php文件:
<?php namespace server; use Workerman\Worker; use Workerman\Connection\TcpConnection; require_once __DIR__ . '/../vendor/autoload.php'; // 创建一个Worker监听2345端口,使用websocket协议通讯 $ws_worker = new Worker("websocket://0.0.0.0:2345"); // 启动4个进程对外提供服务 $ws_worker->count = 4; // 当有客户端连接时 $ws_worker->onConnect = function(TcpConnection $connection) { echo "New connection\n"; }; // 当客户端发来消息时 $ws_worker->onMessage = function(TcpConnection $connection, $data) { $connection->send('Hello ' . $data); }; // 当客户端断开连接时 $ws_worker->onClose = function(TcpConnection $connection) { echo "Connection closed\n"; }; // 运行worker Worker::runAll();3.2 与ThinkPHP8的整合策略
为了使Workerman服务能够使用ThinkPHP的配置和功能,我们需要做以下调整:
- 修改
websocket.php的自动加载部分:
// 替换原来的autoload引入 require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../thinkphp/base.php';- 在消息处理中集成ThinkPHP的功能:
$ws_worker->onMessage = function(TcpConnection $connection, $data) { // 初始化ThinkPHP应用 $http = (new \think\App())->http; $http->run(); // 使用ThinkPHP的日志功能 \think\facade\Log::info('WebSocket message: '.$data); // 业务处理 $response = 'Hello ' . $data; $connection->send($response); };3.3 服务启动与管理
为了方便管理,我们可以创建一个自定义命令来启动Workerman服务。在app/command目录下创建WebSocket.php:
<?php namespace app\command; use think\console\Command; use think\console\Input; use think\console\Output; use Workerman\Worker; class WebSocket extends Command { protected function configure() { $this->setName('websocket:start') ->setDescription('Start WebSocket server'); } protected function execute(Input $input, Output $output) { // 创建一个Worker监听2345端口 $ws_worker = new Worker("websocket://0.0.0.0:2345"); $ws_worker->count = 4; // ... 其他回调函数设置 Worker::runAll(); } }然后在config/console.php中注册这个命令:
return [ 'commands' => [ 'websocket:start' => 'app\command\WebSocket', ], ];现在可以通过以下命令启动服务:
php think websocket:start4. 高级功能实现
4.1 用户认证与会话管理
在实时应用中,用户认证是一个关键问题。我们可以通过以下方式实现:
- 在连接建立时进行Token验证:
$ws_worker->onConnect = function(TcpConnection $connection) { // 获取连接参数中的token $token = $_GET['token'] ?? ''; try { // 使用ThinkPHP的JWT组件验证token $jwt = \think\facade\Jwt::decode($token); $connection->user_id = $jwt->user_id; // 将连接与用户ID关联存储 \app\common\ConnectionMap::add($jwt->user_id, $connection); } catch (\Exception $e) { $connection->close('Authentication failed'); } };- 创建
app/common/ConnectionMap.php来管理连接:
namespace app\common; use Workerman\Connection\TcpConnection; class ConnectionMap { protected static $connections = []; public static function add($userId, TcpConnection $connection) { self::$connections[$userId] = $connection; } public static function get($userId) { return self::$connections[$userId] ?? null; } public static function remove($userId) { unset(self::$connections[$userId]); } }4.2 消息广播与房间管理
实现聊天室功能需要房间管理能力:
// 在server/websocket.php中添加 $ws_worker->onMessage = function(TcpConnection $connection, $data) { $data = json_decode($data, true); switch ($data['type']) { case 'join_room': $connection->room = $data['room']; $connection->send(json_encode([ 'type' => 'system', 'message' => 'Joined room: '.$data['room'] ])); break; case 'chat_message': // 广播给同房间的所有连接 foreach ($ws_worker->connections as $client) { if ($client->room === $connection->room) { $client->send(json_encode([ 'type' => 'chat', 'user' => $connection->user_id, 'message' => $data['message'] ])); } } break; } };4.3 与HTTP接口的交互
有时我们需要从HTTP接口触发WebSocket消息推送:
// 在控制器中 public function notifyUser($userId, $message) { $connection = \app\common\ConnectionMap::get($userId); if ($connection) { $connection->send(json_encode([ 'type' => 'notification', 'message' => $message ])); return true; } return false; }5. 性能优化与生产部署
5.1 进程模型优化
Workerman支持多种进程模型,对于不同场景可以灵活配置:
$ws_worker = new Worker("websocket://0.0.0.0:2345"); // 根据服务器CPU核心数设置进程数 $ws_worker->count = swoole_cpu_num() * 2; // 使用多端口监听提高并发能力 $ws_worker->reusePort = true;5.2 心跳检测与断线重连
保持连接稳定的关键配置:
// 心跳间隔25秒 $ws_worker->pingInterval = 25; // 客户端60秒内没响应则断开 $ws_worker->pingNotResponseLimit = 3; $ws_worker->onWebSocketConnect = function($connection) { // 设置心跳定时器 $connection->timer_id = Timer::add(25, function() use ($connection) { $connection->send(json_encode(['type' => 'ping'])); }); }; $ws_worker->onClose = function($connection) { // 清除定时器 Timer::del($connection->timer_id); };5.3 生产环境部署建议
- 使用Supervisor管理进程:
[program:websocket] command=/usr/bin/php /path/to/project/think websocket:start directory=/path/to/project user=www autostart=true autorestart=true stderr_logfile=/var/log/websocket.err.log stdout_logfile=/var/log/websocket.out.log- Nginx反向代理配置:
server { listen 80; server_name yourdomain.com; location /ws { proxy_pass http://127.0.0.1:2345; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Real-IP $remote_addr; } }6. 常见问题与解决方案
6.1 连接数限制问题
Workerman默认的连接数限制是1024,可以通过以下方式调整:
// 修改全局连接数限制 Worker::$maxConnections = 10000; // 或者在创建worker时指定 $ws_worker = new Worker("websocket://0.0.0.0:2345"); $ws_worker->maxConnections = 10000;6.2 内存泄漏排查
长时间运行后可能出现内存增长问题,可以通过以下方式排查:
- 安装调试组件:
composer require workerman/workerman-psr7- 添加内存监控:
// 每10秒输出一次内存使用情况 Timer::add(10, function() { echo 'Memory usage: '. round(memory_get_usage()/1024/1024, 2) . "MB\n"; });6.3 跨域问题处理
如果前端与WebSocket服务不在同一域名下,需要处理跨域问题:
$ws_worker->onWebSocketConnect = function($connection, $http_header) { // 允许所有来源(生产环境应限制为特定域名) $connection->header = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"; $connection->header .= "Upgrade: websocket\r\n"; $connection->header .= "Connection: Upgrade\r\n"; $connection->header .= "Sec-WebSocket-Accept: " . base64_encode(sha1($http_header['Sec-WebSocket-Key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)) . "\r\n"; $connection->header .= "Access-Control-Allow-Origin: *\r\n\r\n"; };7. 实战案例:在线客服系统
7.1 系统架构设计
基于ThinkPHP8和Workerman的在线客服系统架构:
- Web管理端:使用ThinkPHP开发,处理业务逻辑和数据存储
- WebSocket服务:处理实时消息推送和在线状态管理
- 前端:使用Vue.js + WebSocket实现实时交互
7.2 核心功能实现
- 客服分组与分配:
// 客服分组管理 class CustomerServiceGroup { protected static $groups = []; public static function assign($userId, $groupId) { self::$groups[$groupId][] = $userId; } public static function getAvailableAgent($groupId) { foreach (self::$groups[$groupId] ?? [] as $userId) { if ($connection = ConnectionMap::get($userId)) { return $userId; } } return null; } }- 消息持久化与同步:
// 消息处理中间件 class MessageMiddleware { public static function saveMessage($from, $to, $content) { $message = new \app\model\Message(); $message->save([ 'from_user' => $from, 'to_user' => $to, 'content' => $content, 'create_time' => time() ]); // 推送给接收方 if ($connection = ConnectionMap::get($to)) { $connection->send(json_encode([ 'type' => 'message', 'from' => $from, 'content' => $content, 'time' => date('Y-m-d H:i:s') ])); } } }7.3 性能测试数据
在4核8G的服务器上测试结果:
- 连接数:支持约8000个并发连接
- 消息延迟:平均<50ms
- 内存占用:约300MB(8000空闲连接)
- CPU使用率:约15%(消息频率100条/秒)