在Node.js容器中接收到的MQTT消息没有通过SSE推送到浏览器(Docker在 Raspberry Pi 5上运行)

前端开发 2026-07-10

问题 MQTT消息已成功发布到代理并被我的Node.js容器接收(日志中已确认),但它们并未通过服务器发送事件(SSE)传送到浏览器。浏览器的EventSource仍保持连接,但没有接收到任何事件,界面也从未更新。

预期行为 当我运行mosquitto_pub -t "openclaw/alert" -m "test message" 时,消息应当立即出现在浏览器界面上。

实际行为 消息到达Node.js容器(日志出现),但没有向浏览器推送任何内容。旧数据会一直显示在界面上。

环境

  • 树莓派5
  • Docker Compose
  • Web应用:node:20-slim
  • MQTT代理:eclipse-mosquitto:latest

服务器端代码(web-dashboard.js)

JavaScript

const express = require('express');
const mqttHandler = require('./mqtt-handler.js');

const app = express();
let sseClients = [];

// SSE endpoint
app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  res.write('data: {"type":"connected"}\n\n');

  sseClients.push(res);

  const keepAlive = setInterval(() => {
    res.write(': keep-alive\n\n');
  }, 15000);

  req.on('close', () => {
    clearInterval(keepAlive);
    sseClients = sseClients.filter(c => c !== res);
  });
});

// MQTT → SSE bridge
mqttHandler.client.on('message', (topic, message) => {
  console.log(`📨 MQTT received: ${topic} → ${message.toString()}`);
  if (topic === 'openclaw/alert') {
    const payload = JSON.stringify({ type: 'alert', message: message.toString() });
    sseClients.forEach(client => {
      client.write(`data: ${payload}\n\n`);
    });
  }
});

app.listen(3000, '0.0.0.0', () => {
  console.log("🌐 Dashboard connected to MQTT bus with real-time SSE");
});

客户端代码(浏览器)

JavaScript

const evtSource = new EventSource('/events');
evtSource.onmessage = function(event) {
  const data = JSON.parse(event.data);
  if (data.type === 'alert') {
    const el = document.getElementById('liveEvents');
    el.innerHTML += '<br>' + data.message;
    el.scrollTop = el.scrollHeight;
  }
};

来自docker logs openclaw-web-dashboard的日志

文本

🌐 Dashboard connected to MQTT bus with real-time SSE
📨 MQTT received: openclaw/alert → 🧪 TEST ALERT - Broker is now running in Docker

日志在我每次发布消息时都会出现,但浏览器从未接收到它。

我尝试过的办法

  • docker compose build --no-cache + --force-recreate
  • 清除内存中的缓存
  • 发送保活心跳包
  • 检查Docker网络和端口绑定

有什么想法为什么SSE客户端在服务器处理了MQTT消息的情况下仍然接收不到事件吗?

解决方案

你的问题可能是 TCP套接字缓冲。Node.js以及底层的TCP层默认会对小数据包进行缓冲,这会破坏SSE的实时传输。你的服务器处理MQTT消息并写入响应流,但Nagle算法会延迟发送数据,直到积累足够字节或发生超时。

根据你对日志的描述,似乎 client.write() 的调用在服务端成功执行,但数据包在TCP层被缓冲,而不是立即发送。

尝试在两个层面禁用缓冲:

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });

  res.flushHeaders();           // This forces buffer flush
  req.socket.setNoDelay(true);  // This disables Nagle's algorithm

  res.write('data: {"type":"connected"}\n\n');

  sseClients.push(res);

  const keepAlive = setInterval(() => {
    res.write(': keep-alive\n\n');
  }, 15000);

  req.on('close', () => {
    clearInterval(keepAlive);
    sseClients = sseClients.filter(c => c !== res);
  });
});

简而言之,flushHeaders() 能确保Node.js立即发送缓冲的数据,而 setNoDelay(true) 则禁用TCP的 Nagle算法,防止操作系统对小型SSE负载进行聚合。二者结合应能确保你的MQTT消息实时到达浏览器。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章