You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Node.js、React.js、Socket.io的实时绘图应用AWS部署后无法广播

实时协作绘图Web应用部署后同步功能失效问题

我用Node.js、React.js和Socket.io开发了一款实时协作绘图Web应用,本地运行完全正常——打开多个localhost标签页,在一个画布绘图后,几秒内就能在其他画布同步显示。但部署到AWS后该功能失效,绘图无法在其他画布同步。我也尝试过部署到Render.com等平台,仍存在相同问题。

服务端代码

const express = require('express')
const path = require('path')
const app = express()
app.use(express.static(path.join(__dirname + '/public')))

const PORT = process.env.PORT || 3000

let server = app.listen(PORT);

const io = require('socket.io')(server);
var savedData;
io.on('connection', (socket)=>{
    console.log('a user connected');

    socket.on('canvas-data', (data) => {
        savedData=data
        socket.broadcast.emit('canvas-data', data);

        console.log("canvas-data saved and emitted");
    });

    socket.on('saved-canvas-data', (data)=>{
        io.to(socket.id).emit("saved-canvas-data", savedData);
    });
})

客户端代码

import React from 'react';
import io from 'socket.io-client';

import './style.css';

class Board extends React.Component {

    timeout;
    socket = io.connect("http://192.168.1.72:3000",{'force new connection': true});
    isDrawing = false;
    strokeStyle = "black";
    
    constructor(props){
        super(props);
        this.socket.emit('saved-canvas-data', null);
        this.socket.on('saved-canvas-data', function(data){
            var canvas = document.querySelector('#board');
            var ctx = canvas.getContext('2d');
            var image = new Image();
            image.onload = function(){
                ctx.drawImage(image, 0, 0);
            };
            image.src = data;
        });
        this.socket.on("canvas-data", function(data){
            var root = this;
            var interval = setInterval(function(){
                if(root.isDrawing) return;
                root.isDrawing = true;
                clearInterval(interval);
                var canvas = document.querySelector('#board');
                var ctx = canvas.getContext('2d');
                var image = new Image();
                image.onload = function(){
                    ctx.drawImage(image, 0, 0);
                    root.isDrawing = false;
                };
                image.src = data;
            });
        })
    }
    
    componentDidMount(){
        this.drawOnCanvas();
    }
    
    drawOnCanvas(){
        document.getElementById("black").style.border = "10px solid #ffbf00";
        var canvas = document.querySelector('#board');
        var ctx = canvas.getContext('2d');
    
        var sketch = document.querySelector('#sketch');
        var sketch_style = getComputedStyle(sketch);
        canvas.width = parseInt(sketch_style.getPropertyValue('width'));
        canvas.height = parseInt(sketch_style.getPropertyValue('height'));
    
        var mouse = {x: 0, y: 0};
        var last_mouse = {x: 0, y: 0};
    
        /* Mouse Capturing Work */
        canvas.addEventListener('mousemove', function(e) {
            last_mouse.x = mouse.x;
            last_mouse.y = mouse.y;
    
            mouse.x = e.pageX;
            mouse.y = e.pageY;
        }, false);
    
        /* Drawing on Paint App */
        ctx.lineWidth = 2;
        ctx.lineJoin = 'round';
        ctx.lineCap = 'round';
        ctx.strokeStyle = this.strokeStyle;
    
        canvas.addEventListener("mouseleave", function(event){
            var mouseEvent = new MouseEvent("mouseup", {});
                event.preventDefault();
                canvas.dispatchEvent(mouseEvent);
        });
    
        canvas.addEventListener('mousedown', function(e) {
            canvas.addEventListener('mousemove', onPaint, false);
        }, false);
    
        canvas.addEventListener('mouseup', function() {
            canvas.removeEventListener('mousemove', onPaint, false);
        }, false);
    
        var lastTouchY;
    
        canvas.addEventListener("touchstart", function (e) {
            mouse = {x: e.touches[0].pageX ,y: e.touches[0].pageY };
            var touch = e.touches[0];
            if (e.touches.length < 2){
                e.preventDefault();
            }
            var mouseEvent = new MouseEvent("mousedown", {
                clientX: touch.pageX,
                clientY: touch.pageY
            });
            canvas.dispatchEvent(mouseEvent);
        }, false);
        canvas.addEventListener("touchend", function (e) {
            var mouseEvent = new MouseEvent("mouseup", {});
            if (e.touches.length < 2){
                e.preventDefault();
            }
            canvas.dispatchEvent(mouseEvent);
        }, false);
        canvas.addEventListener("touchmove", function (e) {
            var touch = e.touches[0];
            if (e.touches.length < 2){
                e.preventDefault();
            }
            var mouseEvent = new MouseEvent("mousemove", {
                clientX: touch.pageX,
                clientY: touch.pageY
            });
            canvas.dispatchEvent(mouseEvent);
        }, false);
    
        var root = this;
        var onPaint = function() {
            ctx.beginPath();
            var rect = canvas.getBoundingClientRect();
            // var scrollY = document.body.scrollTop || document.documentElement.scrollTop;
            // var scrollX = document.body.scrollLeft || document.documentElement.scrollLeft;
            var scrollX = window.scrollX;
            var scrollY = window.scrollY;
            ctx.moveTo(last_mouse.x - rect.left - scrollX, last_mouse.y - rect.top - scrollY);
            ctx.lineTo(mouse.x - rect.left - scrollX, mouse.y - rect.top - scrollY);
            ctx.closePath();
            ctx.stroke();
    
            if(root.timeout != undefined) {
                clearTimeout(root.timeout);
            }
            root.timeout = setTimeout(function(){
                var base64ImageData = canvas.toDataURL("image/png");
                root.socket.emit("canvas-data", base64ImageData);
            }, 1000)
        };
    }
    
    changeColor(string){
        var canvas = document.querySelector('#board');
        var ctx = canvas.getContext('2d');
        ctx.strokeStyle = string;
        if (string == "white") {
            document.getElementById("white").style.border = "10px solid #ffbf00";
            document.getElementById("black").style.border = "2px solid black";
        }
         else {
            document.getElementById("black").style.border = "10px solid #ffbf00";
            document.getElementById("white").style.border = "2px solid black";
         }
    }
    
    render(){
        return(
            <div className="sketch" id="sketch">
                <div className="heading" id="heading">
                    <h1>The Board</h1>
                    <p><i>Anyone can draw</i></p>
                </div>
                <div className="colors">
                    <div onClick={() => this.changeColor("black")} className="black" id="black"></div>
                    <div onClick={() => this.changeColor("white")} className="white" id="white"></div>
                </div>
                <canvas className="board" id="board"></canvas>
            </div>
        )
    }

}

export default Board

部署方式

编译React应用,将build文件放入服务端文件夹,构建命令为npm install,启动命令为node index.js。


问题排查与解决方案

1. 客户端Socket连接地址硬编码错误

你客户端代码里固定写了本地IP:socket = io.connect("http://192.168.1.72:3000",{'force new connection': true});,部署到线上后,客户端仍尝试连接这个本地地址,根本无法建立Socket连接。

修复:
改用动态地址适配线上环境,移除废弃配置:

socket = io.connect(window.location.origin, { transports: ['websocket'] });

2. 跨域与WebSocket传输限制

线上环境多使用HTTPS,Socket.io默认的轮询方式可能被浏览器安全策略拦截;部分平台对WebSocket连接有额外要求。

修复:

  • 服务端安装并配置CORS:
const cors = require('cors');
app.use(cors());

// 初始化Socket.io时配置跨域
const io = require('socket.io')(server, {
  cors: {
    origin: "*", // 线上建议指定具体域名,如"https://your-app-domain.com"
    methods: ["GET", "POST"]
  }
});
  • 客户端强制使用WebSocket传输,避免轮询失败(已在第一步配置)。

3. 静态文件路径与路由问题

你当前服务端指向/public目录,但部署时是把React的build文件放入服务端,路径不匹配会导致资源加载失败,间接影响Socket连接。

修复:
修改静态文件路径并添加React路由 fallback:

app.use(express.static(path.join(__dirname, 'build')));

app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

4. 平台端口与监听地址问题

部分平台(如Render、AWS)分配动态端口,且要求服务监听0.0.0.0而非localhost,否则外部无法访问。

修复:
修改服务启动代码:

let server = app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server running on port ${PORT}`);
});

5. 客户端事件绑定上下文错误

客户端canvas-data事件用普通函数绑定,导致this指向异常,isDrawing状态无法正确读取,影响同步逻辑。

修复:
改用箭头函数确保上下文正确:

this.socket.on("canvas-data", (data) => {
  var interval = setInterval(() => {
    if(this.isDrawing) return;
    this.isDrawing = true;
    clearInterval(interval);
    var canvas = document.querySelector('#board');
    var ctx = canvas.getContext('2d');
    var image = new Image();
    image.onload = () => {
      ctx.drawImage(image, 0, 0);
      this.isDrawing = false;
    };
    image.src = data;
  });
})

验证步骤

  1. 部署修复后的代码,打开浏览器控制台,查看Network标签下的ws/polling请求是否正常。
  2. 查看服务端日志,确认有a user connected输出,说明客户端连接成功。
  3. 测试绘图同步,检查服务端是否输出canvas-data saved and emitted,客户端画布是否同步更新。

内容的提问来源于stack exchange,提问作者colonelPickles

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 23:18:11