Image

知识库 → 设置和使用 WebSocket 服务器

[虚拟服务器] [VPS/VDS 上的应用]
出版日期: 31.10.2025

WebSocket 服务器旨在实时获取数据,这与传统 Web 服务器不同。传统服务器在您浏览器中打开链接时获取数据(例如 htmlxmljson 等),它会建立连接 -> 传输数据 -> 断开连接。 WebSocket 服务器的工作方式不同:您与它建立连接后,连接将始终保持活跃。一旦数据发生变化,服务器就会将其传输给客户端——因为它具有双向通信能力,连接始终处于活动状态且不会断开。

1. 应用场景

为了实时获取数据,例如,您希望在单独的网页上或甚至在单独的设备上实时显示您的网站访客或在线商店订单状态。

2. 工作原理

WebSocket 服务器是一个独立的服务,运行在您指定的独立端口上,用于后续连接。WS(WebSocket 服务器)或 WSS(WebSocket 服务器 + SSL)协议允许您配置实时数据输出,例如通过浏览器使用 html + JS

请注意!由于该服务需要打开新端口,因此此类服务的设置和启动仅在虚拟服务器上才可能实现。这正是 WebSocket 服务器不适用于虚拟主机服务的原因。

想象一下,如果每个虚拟主机客户都想运行这样的服务——则需要为每个客户打开一个单独的端口,这将损害服务本身的安全性。无论主机提供商如何,启动和配置都只能在 VPS/VDS 服务器上进行。

3. 任务示例

假设我们有一个 PHP 脚本 https://domain.tld/online-orders.php,它以 JSON 格式生成数据输出,我们希望数据能够实时显示,因为目前实现此结果需要不断刷新。

请注意,虽然对于 PC 和浏览器,可以通过增加请求频率来更新数据,但如果微控制器频繁请求和更新 JSON 数据,它将简单地导致其挂起 1-2 秒。而例如每 20 秒一次的常规 POST/GET 请求,仍然不是真正的实时。

因此,我们需要 WSS。我们将在 Debian 12 上启动一个新的 VPS,采用最小资源配置。WSS 不需要高性能。所有配置都将以 root 用户身份执行。

3.1 服务安装

apt update
apt install -y curl git nodejs npm

3.2 创建 WebSocket 项目

mkdir ~/websocket-server
cd ~/websocket-server
npm init -y
npm install ws node-fetch express

3.3 在 websocket-server 项目根目录中创建配置文件

nano server.js
import express from "express";
import { WebSocketServer } from "ws";
import fetch from "node-fetch";
import https from "https";
import fs from "fs";

// === 设置 ===
const PORT = 8080; // 为当前 WebSocket 服务器选择一个端口
const FETCH_URL = "https://domain.tld/online-orders.php";
const FETCH_INTERVAL = 1000; // 每 1 秒一次

// === HTTPS 证书 ===
// (如果需要 WSS,则需要 SSL 证书。可以使用 Let's Encrypt 或自签名证书)
const options = {
  key: fs.readFileSync("/etc/ssl/private/your-key.pem"),
  cert: fs.readFileSync("/etc/ssl/certs/your-cert.pem")
};

// === HTTP(S) 服务器 + WebSocket ===
const app = express();
const server = https.createServer(options, app);
const wss = new WebSocketServer({ server });

let latestData = null;

// === 定期 JSON 请求 ===
async function updateData() {
  try {
    const res = await fetch(FETCH_URL);
    const json = await res.json();
    latestData = json;

    // 发送给所有已连接的客户端
    wss.clients.forEach(client => {
      if (client.readyState === 1) { // WebSocket.OPEN
        client.send(JSON.stringify(json));
      }
    });
  } catch (err) {
    console.error("获取数据错误:", err);
  }
}

setInterval(updateData, FETCH_INTERVAL);
updateData(); // 首次加载

// === WebSocket 连接 ===
wss.on("connection", ws => {
  console.log("客户端已连接");

  if (latestData) ws.send(JSON.stringify(latestData));

  ws.on("close", () => console.log("客户端已断开连接"));
});

server.listen(PORT, () => {
  console.log(`WSS 服务器已在端口 ${PORT} 上启动`);
});

3.4 通过 Let's Encrypt 配置 WSS 的 SSL

对于 SSL,需要一个域名。我们将使用一个子域名,例如 ws.domain.tld。

apt install certbot python3-certbot-nginx
certbot certonly --standalone -d ws.domain.tld

完成简短的步骤,提供用于通知的电子邮件地址。完成后,证书和密钥将位于此处:

/etc/letsencrypt/live/ws.domain.tld/fullchain.pem
/etc/letsencrypt/live/ws.domain.tld/privkey.pem

替换 server.js 文件中的路径:

const options = {
  key: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/privkey.pem"),
  cert: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/fullchain.pem")
};

3.5 运行和调试

node server.js

我们收到了错误 SyntaxError: Cannot use import statement outside a module

在这种情况下,您需要通过添加行 "type": "module", 来修改 package.json 文件。

package.json 的正确配置示例:

{
  "name": "websocket-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
...

再次运行:

node server.js

WSS 服务器已在端口 8080 上启动
客户端已连接

3.6 配置自动启动:

npm install -g pm2
pm2 start server.js --name websocket
pm2 save
pm2 startup

3.7 检查服务运行状况

pm2 list

3.8 使用 HTML 和 JS 连接

在您的 PC 上创建一个名为 wss-client.html 的文件,内容如下:

您不能像连接 Web 服务器那样直接通过浏览器域名连接 WebSocket;需要创建一个脚本。请指定您自己的域名,而不是 ws.domain.tld。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>WSS 测试</title>
  <style>
    body {
      font-family: monospace;
      background: #111;
      color: #0f0;
      padding: 20px;
    }
    input, button {
      background: #222;
      color: #0f0;
      border: 1px solid #0f0;
      padding: 6px 10px;
      font-family: monospace;
    }
    #log {
      margin-top: 20px;
      white-space: pre-wrap;
      background: #000;
      border: 1px solid #0f0;
      padding: 10px;
      height: 400px;
      overflow-y: auto;
    }
  </style>
</head>
<body>
  <h2>🔌 WebSocket 测试客户端</h2>

  <label>服务器 URL:</label>
  <input id="wsUrl" type="text" size="50" value="wss://ws.domain.tld:8080">
  <button onclick="connect()">连接</button>
  <button onclick="disconnect()">断开连接</button>

  <div id="status">状态: <b>未连接</b></div>
  <div id="log"></div>

  <script>
    let ws;

    function log(msg) {
      const logDiv = document.getElementById('log');
      logDiv.innerText += msg + "\n";
      logDiv.scrollTop = logDiv.scrollHeight;
    }

    function connect() {
      const url = document.getElementById('wsUrl').value;
      ws = new WebSocket(url);

      log("🔄 正在连接到 " + url + " ...");
      document.getElementById('status').innerHTML = "状态: <b>连接中...</b>";

      ws.onopen = () => {
        log("✅ 连接已建立");
        document.getElementById('status').innerHTML = "状态: <b style='color:lime'>已连接</b>";
      };

      ws.onmessage = event => {
        try {
          const json = JSON.parse(event.data);
          log("📦 已接收:\n" + JSON.stringify(json, null, 2));
        } catch (e) {
          log("📦 文本:\n" + event.data);
        }
      };

      ws.onclose = () => {
        log("❌ 连接已关闭");
        document.getElementById('status').innerHTML = "状态: <b style='color:red'>已断开连接</b>";
      };

      ws.onerror = err => {
        log("⚠️ 错误: " + err.message);
      };
    }

    function disconnect() {
      if (ws) {
        ws.close();
        ws = null;
      }
    }
  </script>
</body>
</html>

3.9 查看日志

连接应该可以工作;一旦我们脚本中的数据发生变化,WebSocket 就会为所有连接到它的客户端更新数据。

您可以使用以下命令查看事件日志:

pm2 logs websocket
0|websocket  | 5 次访问已更新

4. 授权

在上面的示例中,任何人都可以连接到 WebSocket 服务器,因此我们添加基于令牌的授权。

更新后的 server.js 代码:

import express from "express";
import { WebSocketServer } from "ws";
import fetch from "node-fetch";
import https from "https";
import fs from "fs";
import url from "url";

// === 设置 ===
const PORT = 8080;
const FETCH_URL = "https://domain.tld/online-orders.php";
const FETCH_INTERVAL = 1000; // 每 1 秒
const VALID_TOKENS = ["ABC123", "ESP32TOKEN", "MYSECRET"]; // 允许的令牌列表

// === SSL ===
const options = {
  key: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/privkey.pem"),
  cert: fs.readFileSync("/etc/letsencrypt/live/ws.domain.tld/fullchain.pem")
};

// === 创建服务器 ===
const app = express();
const server = https.createServer(options, app);
const wss = new WebSocketServer({ server });

let latestData = null;

// === 定期获取 JSON 的函数 ===
async function updateData() {
  try {
    const res = await fetch(FETCH_URL);
    const json = await res.json();
    latestData = json;

    // 发送给所有活跃的客户端
    wss.clients.forEach(client => {
      if (client.readyState === 1) { // WebSocket.OPEN
        client.send(JSON.stringify(json));
      }
    });
  } catch (err) {
    console.error("获取数据错误:", err);
  }
}

setInterval(updateData, FETCH_INTERVAL);
updateData();

// === 处理连接 ===
wss.on("connection", (ws, req) => {
  const query = url.parse(req.url, true).query;
  const token = query.token;

  if (!VALID_TOKENS.includes(token)) {
    console.log(`❌ 连接被拒绝,令牌为: ${token}`);
    ws.close(4001, "Invalid token");
    return;
  }

  console.log(`✅ 客户端已连接,令牌为: ${token}`);

  // 发送最新状态
  if (latestData) ws.send(JSON.stringify(latestData));

  ws.on("close", () => {
    console.log(`🔌 客户端 (${token}) 已断开连接`);
  });
});

server.listen(PORT, () => {
  console.log(`✅ WSS 服务器已在端口 ${PORT} 上启动`);
});

4.1 使用令牌授权连接

对于通过 HTML + JS 的示例,只需将行更改为:

<input id="wsUrl" value="wss://ws.domain.tld:8080/?token=ABC123">

5. 总结

设置完成;现在所有 WebSocket 服务器客户端都使用加密接收实时数据。在我们的示例中,我们使用 PHP 脚本作为系统(通过 API 获取数据)和 WebSocket 服务器(传输数据)之间的中介。

PHP 脚本本身可以移除,可以直接在 server.js 中使用 API 请求并进行系统 API 授权,这将更高效。但是,这样文章就会变得非常庞大,因为它需要涉及 .env 文件中 API 密钥的安全存储以及安装 npm install dotenv 包以处理配置文件数据。





No Comments Yet