PHP 如何实现 WebSocket?
标准的 PHP(如 php-fpm 或 mod_php)是同步阻塞的,每次只能处理一个请求,当它等待 WebSocket 客户端消息时,整个进程就会被阻塞,无法处理其他 HTTP 请求。不能直接用传统的 PHP 脚本来构建一个高性能的 WebSocket 服务器。

要实现一个健壮的 WebSocket 服务器,我们需要 PHP 的 多进程 或 多线程 能力,以下是目前最主流和推荐的几种方法:
Ratchet(PHP 库):这是最流行、最简单的选择,它基于ReactPHP事件循环库,让你能用面向对象的方式轻松构建 WebSocket 服务器,而无需直接处理底层的 socket、多进程等复杂细节。强烈推荐初学者和大多数项目使用。Swoole(PHP 扩展):一个功能极其强大的 PHP 协程框架,它提供了高性能的异步网络通信能力,包括 WebSocket 服务器、HTTP 服务器、TCP/UDP 服务器等,性能远超 Ratchet,但需要安装 PECL 扩展,学习曲线稍陡。Workerman(PHP 库):另一个高性能的 PHP socket 服务器框架,与 Swoole 类似,支持长连接、异步、多进程,它不依赖ReactPHP,有自己的实现。
使用 Ratchet (最推荐,最简单)
这个方案非常适合快速开发和中小型应用。
步骤 1: 安装 Ratchet
你需要通过 Composer 安装 Ratchet。
# 在你的项目根目录下执行 composer require cboden/ratchet
步骤 2: 编写 WebSocket 服务器代码
创建一个文件,server.php,这个文件将定义一个处理 WebSocket 连接、消息和断开连接的类。

<?php
// server.php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
// 1. 创建一个处理消息的类
class Chat implements MessageComponentInterface {
// 存储所有连接的客户端
protected $clients;
public function __construct() {
// 使用一个 SplObjectStorage 来存储客户端,它是一个高性能的对象集合
$this->clients = new \SplObjectStorage;
}
// 当有新的客户端连接时触发
public function onOpen(ConnectionInterface $conn) {
// 将新连接添加到客户端列表
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
// 当从客户端接收到消息时触发
public function onMessage(ConnectionInterface $from, $msg) {
$numRecv = count($this->clients) - 1;
echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
, $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
// 将收到的消息广播给所有其他客户端
foreach ($this->clients as $client) {
if ($from !== $client) {
// 发送消息
$client->send($msg);
}
}
}
// 当客户端断开连接时触发
public function onClose(ConnectionInterface $conn) {
// 从客户端列表中移除断开的连接
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
// 当发生错误时触发
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
}
// 2. 运行服务器
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
// 创建一个 WebSocket 服务器,监听所有网络接口的 8080 端口
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8080
);
echo "Chat server started on ws://localhost:8080\n";
// 启动服务器并开始循环监听
$server->run();
步骤 3: 启动服务器
在终端中,运行 server.php 文件。注意: 这个脚本需要持续运行,不能关闭终端。
php server.php
你会看到输出:
Chat server started on ws://localhost:8080
你的 WebSocket 服务器已经在 8080 端口上运行了。
步骤 4: 创建一个简单的 HTML/JavaScript 客户端来测试
创建一个 index.html 文件:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">PHP WebSocket Chat</title>
<style>
body { font-family: Arial, sans-serif; }
#chat-log { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
#message-input { width: 80%; padding: 5px; }
#send-button { width: 18%; padding: 5px; }
</style>
</head>
<body>
<h1>PHP WebSocket Chat</h1>
<div id="chat-log"></div>
<input type="text" id="message-input" placeholder="Type a message...">
<button id="send-button">Send</button>
<script>
// 连接到 WebSocket 服务器
// 注意:如果你的服务器不在本机,请将 localhost 替换为服务器 IP
const socket = new WebSocket('ws://localhost:8080');
const chatLog = document.getElementById('chat-log');
const messageInput = document.getElementById('message-input');
const sendButton = document.getElementById('send-button');
// 连接建立时
socket.onopen = function(event) {
console.log('Connection established!');
addToLog('Connected to the chat server.');
};
// 收到消息时
socket.onmessage = function(event) {
console.log('Message received:', event.data);
addToLog(event.data);
};
// 连接关闭时
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
console.error('Connection died');
}
addToLog('Disconnected from the chat server.');
};
// 发生错误时
socket.onerror = function(error) {
console.error(`WebSocket Error: ${error.message}`);
addToLog('An error occurred.');
};
// 发送消息
function sendMessage() {
const message = messageInput.value;
if (message) {
socket.send(message);
messageInput.value = '';
}
}
// 添加消息到日志
function addToLog(message) {
const p = document.createElement('p');
p.textContent = message;
chatLog.appendChild(p);
chatLog.scrollTop = chatLog.scrollHeight;
}
// 绑定发送按钮事件
sendButton.addEventListener('click', sendMessage);
// 绑定回车键发送
messageInput.addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
用浏览器打开 index.html 文件,打开两个浏览器窗口(或两个标签页),你可以在一个窗口输入消息,然后在另一个窗口看到它,这就是一个简单的聊天室!
使用 Swoole (高性能方案)
如果你的应用对性能有极高要求,可以考虑 Swoole。
步骤 1: 安装 Swoole 扩展
你需要先编译并安装 Swoole PECL 扩展。
# (CentOS/RHEL) pecl install swoole # (Ubuntu/Debian) sudo apt-get install php-dev php-pear sudo pecl install swoole # 然后在 php.ini 中添加 extension=swoole
步骤 2: 编写 Swoole WebSocket 服务器代码
创建 swoole_server.php 文件:
<?php
// swoole_server.php
$server = new Swoole\WebSocket\Server("0.0.0.0", 9501);
// 当有新的 WebSocket 连接进入时
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
echo "New connection: {$request->fd}\n";
// 可以在这里将用户ID与fd关联,
// $server->bind($request->fd, $userId);
});
// 当收到客户端消息时
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
echo "Received message: {$frame 