PHP WebSocket服务器搭建指南

发布于:2025-07-02 ⋅ 阅读:(19) ⋅ 点赞:(0)

        该代码实现了一个原生的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开启服务监听,然后再把浏览器打开,就可以发送信息且接收到信息了。


网站公告

今日签到

点亮在社区的每一天
去签到