1 下载扩展
# 在 ThinkPHP 5.1 项目根目录执行 Composer 安装
composer require cboden/ratchet2 创建 WebSocket 服务命令
在 application/command 目录下创建 RatchetServer.php(命令行启动类)
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use Ratchet\Server\IoServer; // Ratchet的IO服务器类,用于监听端口和处理连接
use Ratchet\Http\HttpServer; // Ratchet的HTTP服务器类,处理HTTP协议相关逻辑
use Ratchet\WebSocket\WsServer; // Ratchet的WebSocket服务器类,处理WebSocket协议
use Ratchet\Http\HttpRequest; // Ratchet的HTTP请求类,用于解析HTTP请求
/**
* 自定义HTTP服务器类
* 继承Ratchet的HttpServer,主要用于处理跨域请求头
*/
class CorsHttpServer extends HttpServer
{
// 允许跨域访问的域名列表(根据实际情况修改)
protected $allowedOrigins = [
'http://www.cmstp50.com', // 你的本地域名
'http://localhost' // 本地测试域名
];
/**
* 重写HTTP请求处理方法
* 主要功能:添加跨域响应头,处理预检请求
* @param HttpRequest $request 客户端请求对象
* @param mixed $response 服务端响应对象
*/
public function onRequest(HttpRequest $request, $response)
{
// 获取客户端请求头中的Origin(来源域名)
$origin = $request->getHeader('Origin', '');
// 如果来源域名在允许列表中,添加跨域响应头
if (in_array($origin, $this->allowedOrigins)) {
$response->addHeader('Access-Control-Allow-Origin', $origin); // 允许指定域名跨域
$response->addHeader('Access-Control-Allow-Credentials', 'true'); // 允许携带Cookie
$response->addHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); // 允许的请求方法
$response->addHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); // 允许的请求头
}
// 处理OPTIONS预检请求(浏览器在复杂请求前会发送该请求验证跨域)
if ($request->getMethod() === 'OPTIONS') {
$response->setStatusCode(204); // 204表示无内容,预检请求无需返回数据
$response->end(); // 结束响应
return;
}
// 调用父类方法处理正常的HTTP/WebSocket请求
parent::onRequest($request, $response);
}
}
/**
* 自定义消息处理类
* 实现Ratchet的MessageComponentInterface接口,处理WebSocket的核心逻辑(连接、消息、关闭、错误)
*/
class ChatHandler implements \Ratchet\MessageComponentInterface
{
protected $clients; // 存储所有连接的客户端(使用SplObjectStorage便于对象管理)
/**
* 构造方法:初始化客户端存储容器
*/
public function __construct()
{
$this->clients = new \SplObjectStorage; // 实例化对象存储容器
echo "初始化聊天服务...\n";
}
/**
* 客户端连接时触发
* @param \Ratchet\ConnectionInterface $conn 客户端连接对象(包含连接ID等信息)
*/
public function onOpen(\Ratchet\ConnectionInterface $conn)
{
$this->clients->attach($conn); // 将新连接的客户端添加到存储容器
// 向当前连接的客户端发送欢迎消息(包含其唯一ID)
$conn->send("欢迎连接!你的ID:{$conn->resourceId}");
// 在服务端控制台打印连接信息(包含当前在线人数)
echo "新连接:{$conn->resourceId}(当前在线:" . $this->clients->count() . ")\n";
}
/**
* 收到客户端消息时触发
* @param \Ratchet\ConnectionInterface $from 发送消息的客户端连接
* @param string $msg 收到的消息内容
*/
public function onMessage(\Ratchet\ConnectionInterface $from, $msg)
{
$clientCount = $this->clients->count(); // 获取当前在线客户端数量
// 在服务端控制台打印消息信息
echo "用户{$from->resourceId}发送消息:{$msg}(广播给{$clientCount}个客户端)\n";
// 遍历所有在线客户端,将消息广播给所有人(包括发送者自己)
foreach ($this->clients as $client) {
$client->send("用户{$from->resourceId}:{$msg}");
}
}
/**
* 客户端断开连接时触发
* @param \Ratchet\ConnectionInterface $conn 断开连接的客户端对象
*/
public function onClose(\Ratchet\ConnectionInterface $conn)
{
$this->clients->detach($conn); // 从存储容器中移除断开的客户端
// 在服务端控制台打印断开信息(更新在线人数)
echo "连接关闭:{$conn->resourceId}(当前在线:" . $this->clients->count() . ")\n";
}
/**
* 连接发生错误时触发
* @param \Ratchet\ConnectionInterface $conn 发生错误的客户端连接
* @param \Exception $e 错误对象
*/
public function onError(\Ratchet\ConnectionInterface $conn, \Exception $e)
{
// 在服务端控制台打印错误信息
echo "错误(用户{$conn->resourceId}):{$e->getMessage()}\n";
$conn->close(); // 发生错误时关闭连接
}
}
/**
* 命令行类:用于通过thinkphp命令启动WebSocket服务
*/
class RatchetServer extends Command
{
/**
* 配置命令信息(名称和描述)
*/
protected function configure()
{
$this->setName('ratchet') // 命令名称:php think ratchet
->setDescription('启动 Ratchet WebSocket 服务'); // 命令描述
}
/**
* 执行命令:启动WebSocket服务
* @param Input $input 命令输入对象
* @param Output $output 命令输出对象
*/
protected function execute(Input $input, Output $output)
{
// 创建WebSocket服务实例
$server = IoServer::factory(
new CorsHttpServer( // 使用自定义的跨域HTTP服务器
new WsServer( // WebSocket协议处理层
new ChatHandler() // 自定义消息处理逻辑
)
),
8080 // 监听的端口号
);
// 在控制台输出启动成功信息
$output->writeln("Ratchet WebSocket 服务启动,监听端口 8080...");
// 启动服务(进入事件循环,持续监听连接和消息)
$server->run();
}
}3 注册命令
在 application/command.php 中注册命令(若文件不存在则创建)
<?php
return [
'app\command\RatchetServer'
];4 创建客户端测试页面
在【根目录\application\index\view\index】新建index.html文件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 实时通信测试</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { padding: 20px; font-family: Arial, sans-serif; }
h3 { margin-bottom: 20px; color: #333; }
.input-group { display: flex; gap: 10px; margin-bottom: 15px; }
#msg { flex: 1; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
button { padding: 8px 20px; background: #007bff; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
button:hover { background: #0056b3; }
#log {
height: 300px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
margin-top: 10px;
overflow-y: auto;
background: #f9f9f9;
}
#log div { margin: 5px 0; padding: 6px; border-radius: 3px; }
#log div:nth-child(odd) { background: #fff; }
.self-msg { color: #007bff; font-weight: 500; }
.system-msg { color: #666; font-style: italic; }
.error-msg { color: #dc3545; }
</style>
</head>
<body>
<h3>Ratchet WebSocket 实时测试</h3>
<div class="input-group">
<input type="text" id="msg" placeholder="输入消息后按回车或点击发送" autocomplete="off">
<button onclick="send()">发送</button>
</div>
<div id="log"></div>
<script>
// 连接 WebSocket 服务(使用你的本地域名和 8080 端口)
const ws = new WebSocket('ws://www.cmstp50.com:8080');
// 连接成功
ws.onopen = () => {
log('连接已建立,可正常发送消息', 'system-msg');
};
// 接收服务端消息
ws.onmessage = (e) => {
log(e.data);
};
// 连接错误(关键:快速定位问题)
ws.onerror = (error) => {
log(`连接错误:${error.message || '未知错误'}`, 'error-msg');
};
// 连接关闭
ws.onclose = (event) => {
let reason = '';
switch(event.code) {
case 1000: reason = '正常关闭'; break;
case 1006: reason = '服务端未启动或端口占用'; break;
default: reason = `错误码:${event.code},原因:${event.reason}`;
}
log(`连接关闭:${reason}`, 'error-msg');
};
// 发送消息
function send() {
const msgInput = document.getElementById('msg');
const msg = msgInput.value.trim();
if (!msg) return;
// 检查连接状态
if (ws.readyState !== WebSocket.OPEN) {
log('发送失败:连接未建立或已关闭', 'error-msg');
return;
}
// 发送消息并即时显示自己的消息
ws.send(msg);
log(`我:${msg}`, 'self-msg');
msgInput.value = '';
msgInput.focus();
}
// 显示日志到页面
function log(text, className = '') {
const logDiv = document.getElementById('log');
const time = new Date().toLocaleTimeString(); // 本地时间
const msgElement = document.createElement('div');
msgElement.className = className;
msgElement.innerHTML = `<span style="color:#999; font-size:12px;">${time}</span>:${text}`;
logDiv.appendChild(msgElement);
// 自动滚动到底部
logDiv.scrollTop = logDiv.scrollHeight;
}
// 支持回车键发送
document.getElementById('msg').addEventListener('keydown', (e) => {
if (e.key === 'Enter') send();
});
// 页面加载完成后自动聚焦输入框
window.onload = () => {
document.getElementById('msg').focus();
};
</script>
</body>
</html>5 启动服务并测试
(1) 启动 WebSocket 服务:在项目根目录执行命令
php think ratchet(2) 测试效果:浏览器打开首页,效果如下:

智享笔记