该代码实现了一个原生的WebSocket服务器,与客户端保持长连接,接受信息后,广播通知其他链接的客户端。
以下是服务器端代码:
<?php
class WebSocketServer
{
private $sockets = [];
private $master;
public function __construct($host = '0.0.0.0', $port = 8000)
{
$this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->master, $host, $port);
socket_listen($this->master);
$this->sockets[] = $this->master;
echo "Server started on ws://{$host}:{$port}\n";
}
private function handshake($buffer, $socket)
{
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $buffer, $match))
{
$key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
$headers = "HTTP/1.1 101 Switching Protocols\r\n";
$headers .= "Upgrade: websocket\r\n";
$headers .= "Connection: Upgrade\r\n";
$headers .= "Sec-WebSocket-Accept: $key\r\n\r\n";
socket_write($socket, $headers, strlen($headers));
return true;
}
return false;
}
private function unmask($payload) {
$length = ord($payload[1]) & 127;
if ($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
} elseif ($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
} else {
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i % 4];
}
return $text;
}
private function mask($text) {
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if ($length <= 125) {
$header = pack('CC', $b1, $length);
} elseif ($length > 125 && $length < 65536) {
$header = pack('CCn', $b1, 126, $length);
} else {
$header = pack('CCNN', $b1, 127, $length);
}
return $header . $text;
}
public function run() {
while (true)
{
$changed = $this->sockets;
$write = $except = null; // 显式赋 null
socket_select($changed, $write, $except, null);
foreach ($changed as $socket)
{
//活跃socket是新生成的对象
if ($socket == $this->master)
{
$client = socket_accept($this->master);
$this->sockets[] = $client;
}
else
{
$bytes = @socket_recv($socket, $buffer, 2048, 0);
//客户端关闭
if ($bytes === 0)
{
$index = array_search($socket, $this->sockets);
unset($this->sockets[$index]);
socket_close($socket);
}
else
{
//websocket握手升级协议开始建立长连接
if (stripos($buffer,'Sec-WebSocket-Key'))
{
$this->handshake($buffer, $socket);
}
else
{
$message = $this->unmask($buffer);
echo "Received: {$message}\n";
$response = $this->mask("Server received: {$message}");
//通知所有链接的客户端
foreach($this->sockets as $socketItem)
{
//排除总机监听的socket类
if($this->master != $socketItem)
{
socket_write($socketItem, $response, strlen($response));
}
}
}
}
}
}
}
}
}
$server = new WebSocketServer();
$server->run();
客户端代码:
<!DOCTYPE html>
<html>
<head>
<title>Socket客户端</title>
<style>
#console { height:300px; border:1px solid #ccc; overflow-y:scroll; padding:10px; }
.status { color:#666; margin:10px 0; }
</style>
</head>
<body>
<div class="status" id="status">准备连接服务器...</div>
<div id="console"></div>
<input type="text" id="message" placeholder="输入消息">
<button onclick="send()">发送</button>
<script>
const socket = new WebSocket('ws://localhost:8000');
const statusEl = document.getElementById('status');
socket.onopen = () => {
statusEl.textContent = "已连接到服务器";
log('系统: 连接成功');
};
socket.onmessage = (e) => {
log('服务器: ' + e.data);
};
socket.onerror = (e) => {
statusEl.textContent = "连接错误";
log('系统: 错误: ' + e.message);
};
socket.onclose = () => {
statusEl.textContent = "连接已关闭";
log('系统: 连接断开');
};
function send() {
const msg = document.getElementById('message').value;
if(socket.readyState === WebSocket.OPEN) {
socket.send(msg);
log('我: ' + msg);
document.getElementById('message').value = '';
}
}
function log(msg) {
const console = document.getElementById('console');
const p = document.createElement('p');
p.textContent = msg;
console.appendChild(p);
console.scrollTop = console.scrollHeight;
}
</script>
</body>
</html>
这样两部分就都开发完毕了。
然后我们用命令行启动php开启服务监听,然后再把浏览器打开,就可以发送信息且接收到信息了。