Ratchet 是一个基于 ReactPHP 的 PHP WebSocket 库,无需依赖 Swoole 扩展。以下是实现步骤:
首先安装 Ratchet:
composer require cboden/ratchet
创建 WebSocket 处理类:
<?php
/*** websocket处理类* @DateTime 2025/7/28 10:38*/namespace app\api\controller;use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;class WebSocketSever implements MessageComponentInterface
{protected $clients;public function __construct(){$this->clients = new \SplObjectStorage;echo "Chat server initialized\n";}// 实现接口要求的四个方法public function onOpen(ConnectionInterface $conn){$this->clients->attach($conn);$clientId = $conn->resourceId;echo "新客户端连接: ({$clientId})\n";$conn->send("连接成功!你的客户端ID是: {$clientId}");}public function onMessage(ConnectionInterface $from, $msg){$clientId = $from->resourceId;echo "客户端 {$clientId} 发送消息: {$msg}\n";// 向发送者确认$from->send("服务器已收到: {$msg}");// 广播给其他客户端foreach ($this->clients as $client) {if ($from !== $client) {$client->send("用户 {$clientId} 说: {$msg}");}}}public function onClose(ConnectionInterface $conn){$this->clients->detach($conn);echo "客户端 {$conn->resourceId} 断开连接\n";}public function onError(ConnectionInterface $conn, \Exception $e){echo "发生错误: {$e->getMessage()}\n";$conn->close();}
}
创建 WebSocket 服务启动文件start_server.php:
注意:这里不能使用控制器,不要问为什么,问就是我已经尝试过了,当然你也可以尝试一下。
<?php
/*** websocket启动文件 start_server.php* @DateTime 2025/7/28 16:14*/
require 'vendor/autoload.php';use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use app\api\controller\WebSocketSever;$server =IoServer::factory(new HttpServer(new WsServer(new WebSocketSever())),9501 // 端口号
);echo "Ratchet WebSocket服务器已启动: ws://127.0.0.1:9501\n";
echo "按 Ctrl+C 停止服务\n";
$server->run();
前端测试页面:
<!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><script src="https://cdn.tailwindcss.com"></script><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><!-- 配置Tailwind自定义颜色和动画 --><script>tailwind.config = {theme: {extend: {colors: {primary: '#3B82F6',secondary: '#10B981',danger: '#EF4444',dark: '#1E293B',light: '#F8FAFC'},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},animation: {'fade-in': 'fadeIn 0.3s ease-in-out',},keyframes: {fadeIn: {'0%': { opacity: '0' },'100%': { opacity: '1' },}}}}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.scrollbar-hide {scrollbar-width: none;-ms-overflow-style: none;}.scrollbar-hide::-webkit-scrollbar {display: none;}.message-box {@apply rounded-lg p-4 mb-3 max-w-[80%] animate-fade-in;}.sent-message {@apply bg-primary text-white ml-auto;}.received-message {@apply bg-gray-200 text-dark mr-auto;}.system-message {@apply bg-gray-100 text-gray-500 text-sm mx-auto;}}</style>
</head>
<body class="bg-gray-50 min-h-screen font-sans">
<div class="container mx-auto px-4 py-8 max-w-4xl"><header class="mb-8 text-center"><h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-dark mb-2"><i class="fa fa-comments text-primary mr-2"></i>WebSocket 测试工具</h1><p class="text-gray-600">与 Ratchet WebSocket 服务器的实时通信测试</p></header><!-- 连接状态区域 --><div class="bg-white rounded-xl shadow-md p-4 mb-6 transition-all duration-300"><div class="flex items-center justify-between"><div class="flex items-center"><div id="connection-status" class="w-3 h-3 rounded-full bg-danger mr-2"></div><span id="status-text" class="text-danger font-medium">未连接</span></div><div class="flex space-x-2"><input type="text" id="server-url"value="ws://127.0.0.1:9501"class="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 text-sm w-64"><button id="connect-btn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg transition-all duration-200 flex items-center"><i class="fa fa-plug mr-1"></i> 连接</button></div></div><div class="mt-3 text-sm text-gray-500 flex items-center"><i class="fa fa-info-circle mr-1"></i><span>服务器状态: <span id="server-info">等待连接...</span></span></div></div><!-- 消息显示区域 --><div class="bg-white rounded-xl shadow-md p-4 mb-6 h-[500px] flex flex-col"><div class="text-sm font-medium text-gray-500 mb-3 border-b pb-2"><i class="fa fa-history mr-1"></i> 消息历史</div><div id="messages" class="flex-1 overflow-y-auto scrollbar-hide p-2 space-y-2"><!-- 消息会动态添加到这里 --><div class="system-message message-box"><i class="fa fa-info-circle mr-1"></i>请点击"连接"按钮与服务器建立连接</div></div></div><!-- 消息输入区域 --><div class="bg-white rounded-xl shadow-md p-4"><div class="flex space-x-3"><textareaid="messageInput"placeholder="输入消息内容...(按Enter发送,Shift+Enter换行)"class="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/50 resize-none"rows="3"></textarea><buttonid="send-btn"class="bg-secondary hover:bg-secondary/90 text-white px-6 py-3 rounded-lg transition-all duration-200 flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed"disabled><i class="fa fa-paper-plane mr-1"></i> 发送</button></div><div class="mt-3 text-xs text-gray-400 flex justify-between"><div><i class="fa fa-keyboard-o mr-1"></i> 快捷键: Enter发送消息</div><div id="client-id" class="hidden"><i class="fa fa-user mr-1"></i> 客户端ID: <span></span></div></div></div><footer class="mt-8 text-center text-gray-500 text-sm"><p>WebSocket 测试工具 © 2023</p></footer>
</div><script>// DOM元素const connectBtn = document.getElementById('connect-btn');const sendBtn = document.getElementById('send-btn');const messageInput = document.getElementById('messageInput');const messagesDiv = document.getElementById('messages');const connectionStatus = document.getElementById('connection-status');const statusText = document.getElementById('status-text');const serverInfo = document.getElementById('server-info');const serverUrlInput = document.getElementById('server-url');const clientIdElement = document.getElementById('client-id').querySelector('span');const clientIdContainer = document.getElementById('client-id');// WebSocket实例let ws = null;let isConnected = false;// 连接状态样式更新function updateConnectionStatus(connected) {isConnected = connected;if (connected) {connectionStatus.classList.remove('bg-danger');connectionStatus.classList.add('bg-secondary');statusText.classList.remove('text-danger');statusText.classList.add('text-secondary');statusText.textContent = '已连接';connectBtn.innerHTML = '<i class="fa fa-unplug mr-1"></i> 断开';connectBtn.classList.remove('bg-primary');connectBtn.classList.add('bg-danger');sendBtn.disabled = false;messageInput.focus();serverInfo.textContent = `已连接到 ${serverUrlInput.value}`;clientIdContainer.classList.remove('hidden');} else {connectionStatus.classList.remove('bg-secondary');connectionStatus.classList.add('bg-danger');statusText.classList.remove('text-secondary');statusText.classList.add('text-danger');statusText.textContent = '未连接';connectBtn.innerHTML = '<i class="fa fa-plug mr-1"></i> 连接';connectBtn.classList.remove('bg-danger');connectBtn.classList.add('bg-primary');sendBtn.disabled = true;serverInfo.textContent = '未连接到服务器';clientIdContainer.classList.add('hidden');}}// 显示消息function showMessage(text, type = 'received') {const messageElement = document.createElement('div');// 设置消息类型样式if (type === 'sent') {messageElement.classList.add('sent-message', 'message-box');} else if (type === 'received') {messageElement.classList.add('received-message', 'message-box');} else if (type === 'system') {messageElement.classList.add('system-message', 'message-box');}// 添加消息内容messageElement.textContent = text;messagesDiv.appendChild(messageElement);// 滚动到底部messagesDiv.scrollTop = messagesDiv.scrollHeight;}// 连接/断开WebSocketfunction toggleConnection() {if (isConnected) {// 断开连接if (ws) {ws.close();}} else {// 建立连接const serverUrl = serverUrlInput.value.trim();if (!serverUrl) {showMessage('请输入服务器地址', 'system');return;}try {ws = new WebSocket(serverUrl);// 连接成功ws.onopen = function() {updateConnectionStatus(true);showMessage('成功连接到服务器', 'system');};// 接收消息ws.onmessage = function(evt) {console.log('收到消息: ' + evt.data);// 尝试提取客户端ID(如果服务器返回)if (evt.data.startsWith('你的客户端ID是:')) {const clientId = evt.data.split(':')[1].trim();clientIdElement.textContent = clientId;}showMessage(evt.data, 'received');};// 连接关闭ws.onclose = function(event) {updateConnectionStatus(false);let message = '连接已关闭';if (event.code !== 1000) {message += ` (错误代码: ${event.code})`;}showMessage(message, 'system');};// 连接错误ws.onerror = function(error) {showMessage(`连接错误: ${error.message || '未知错误'}`, 'system');console.error('WebSocket错误:', error);};} catch (error) {showMessage(`连接失败: ${error.message}`, 'system');console.error('连接异常:', error);}}}// 发送消息function sendMessage() {if (!isConnected || !ws) {showMessage('请先连接到服务器', 'system');return;}const message = messageInput.value.trim();if (!message) return;try {ws.send(message);showMessage(`我: ${message}`, 'sent');messageInput.value = '';} catch (error) {showMessage(`发送失败: ${error.message}`, 'system');console.error('发送消息错误:', error);}}// 事件监听connectBtn.addEventListener('click', toggleConnection);sendBtn.addEventListener('click', sendMessage);// 处理文本框回车事件messageInput.addEventListener('keydown', function(e) {// Enter键且没有按Shiftif (e.key === 'Enter' && !e.shiftKey) {e.preventDefault(); // 阻止默认换行sendMessage();}});// 服务器地址输入框事件serverUrlInput.addEventListener('keydown', function(e) {if (e.key === 'Enter') {e.preventDefault();if (!isConnected) {toggleConnection();}}});
</script>
</body>
</html>
启动服务:
php start_server.php
启动结果:
访问测试页面即可进行 WebSocket 通信。
点击【连接】后的页面:
与 Swoole 相比,Ratchet 的优势是不需要安装 PHP 扩展,纯 PHP 实现,兼容性更好;缺点是在高并发场景下性能可能不如 Swoole。
下一篇文章尝试使用Swoole来搭建websocket服务。