本文目录导读:

PHP实现WebSocket的难度是相对复杂的,但这取决于你的具体需求,让我给你一个全面的分析:
难度评估
简单场景(难度:★★☆☆☆)
- 使用现成的WebSocket服务器库(如Ratchet、Swoole)
- 基础的聊天室、实时通知
- 不需要处理高并发
复杂场景(难度:★★★★☆)
- 从零实现WebSocket协议
- 处理高并发连接
- 分布式部署和消息广播
- 需要断线重连、心跳机制
常用实现方式
使用现成库(推荐)
Ratchet(纯PHP实现):
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
require __DIR__ . '/vendor/autoload.php';
class Chat implements MessageComponentInterface {
protected $clients;
public function __construct() {
$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) {
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();
}
}
$server = IoServer::factory(
new HttpServer(
new WsServer(
new Chat()
)
),
8080
);
echo "Server started on port 8080\n";
$server->run();
使用Swoole(性能更好)
<?php
use Swoole\WebSocket\Server;
$server = new Server("0.0.0.0", 9502);
$server->on('open', function (Server $server, $request) {
echo "New client connected: {$request->fd}\n";
});
$server->on('message', function (Server $server, $frame) {
echo "Received message: {$frame->data}\n";
$server->push($frame->fd, "Server: " . $frame->data);
});
$server->on('close', function ($ser, $fd) {
echo "Client {$fd} closed\n";
});
$server->start();
从零实现WebSocket(了解原理)
如果你需要理解底层原理,这里是基本框架:
<?php
class SimpleWebSocket {
private $socket;
private $clients = [];
public function __construct($host = '0.0.0.0', $port = 8080) {
$this->socket = stream_socket_server("tcp://$host:$port", $errno, $errstr);
if (!$this->socket) {
die("Error: $errstr ($errno)");
}
echo "WebSocket server started on $host:$port\n";
$this->run();
}
private function run() {
while (true) {
$read = array_merge([$this->socket], $this->clients);
$write = $except = null;
if (stream_select($read, $write, $except, 10) > 0) {
if (in_array($this->socket, $read)) {
$newClient = stream_socket_accept($this->socket);
$this->clients[] = $newClient;
echo "New client connected\n";
}
foreach ($read as $client) {
if ($client === $this->socket) continue;
$data = fread($client, 2048);
if ($data) {
$this->processData($client, $data);
} else {
// Client disconnected
$this->removeClient($client);
}
}
}
}
}
private function processData($client, $data) {
if (strpos($data, 'Sec-WebSocket-Key') !== false) {
// Handshake
$this->performHandshake($client, $data);
} else {
// Parse and handle WebSocket frames
$this->handleFrame($client, $data);
}
}
private function performHandshake($client, $data) {
preg_match('/Sec-WebSocket-Key:\s*(.*)\r\n/', $data, $matches);
$key = trim($matches[1]);
$accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
$response = "HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: $accept\r\n\r\n";
fwrite($client, $response);
}
private function handleFrame($client, $data) {
// 解析WebSocket帧
$firstByte = ord($data[0]);
$opcode = $firstByte & 0x0F;
$secondByte = ord($data[1]);
$masked = ($secondByte & 0x80) >> 7;
$length = $secondByte & 0x7F;
$offset = 2;
if ($length == 126) {
$length = unpack('n', substr($data, 2, 2))[1];
$offset = 4;
} elseif ($length == 127) {
$length = unpack('J', substr($data, 2, 8))[1];
$offset = 10;
}
$maskingKey = substr($data, $offset, 4);
$offset += 4;
$payload = '';
for ($i = 0; $i < $length; $i++) {
$payload .= $data[$offset + $i] ^ $maskingKey[$i % 4];
}
// 处理消息
if ($opcode == 0x8) { // Close
fclose($client);
$this->removeClient($client);
} elseif ($opcode == 0x9) { // Ping
$this->sendFrame($client, $payload, 0xA); // Pong
} else {
echo "Received: $payload\n";
// 广播给所有客户端
foreach ($this->clients as $c) {
$this->sendFrame($c, "Echo: $payload", 0x1);
}
}
}
private function sendFrame($client, $message, $opcode = 0x1) {
$len = strlen($message);
if ($len < 126) {
$frame = chr($opcode | 0x80) . chr($len);
} elseif ($len < 65536) {
$frame = chr($opcode | 0x80) . chr(126) . pack('n', $len);
} else {
$frame = chr($opcode | 0x80) . chr(127) . pack('J', $len);
}
fwrite($client, $frame . $message);
}
private function removeClient($client) {
$index = array_search($client, $this->clients);
if ($index !== false) {
unset($this->clients[$index]);
}
}
}
$server = new SimpleWebSocket();
前端JavaScript配合
<!DOCTYPE html>
<html>
<head>WebSocket Test</title>
</head>
<body>
<div id="messages"></div>
<input type="text" id="message">
<button onclick="sendMessage()">Send</button>
<script>
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected to server');
ws.send('Hello Server!');
};
ws.onmessage = (event) => {
document.getElementById('messages').innerHTML += event.data + '<br>';
};
ws.onclose = () => {
console.log('Disconnected');
};
function sendMessage() {
const msg = document.getElementById('message').value;
ws.send(msg);
}
</script>
</body>
</html>
建议
- 初学者:直接使用Ratchet或Swoole,理解WebSocket协议
- 生产环境:使用Swoole(性能好)或Workerman(纯PHP)
- 学习原理:从零实现,了解握手、帧解析、掩码处理
- 注意点:
- 心跳检测和断线重连
- 跨域问题(CORS)
- 消息大小和频率限制
- 使用SSL(wss://)加密
- 简单方式:用现成库,20分钟就能跑起来
- 复杂方式:从零实现,需要深入理解协议细节
如果只是做项目,强烈建议使用现成库,把精力放在业务逻辑上,如果你想深入学习WebSocket原理,从零实现是很好的学习方式。