面试题答案
一键面试整体架构
- 客户端:
- 用户界面:使用HTML5的Canvas元素来绘制图形。用户通过鼠标或触摸事件在Canvas上进行绘图操作。
- WebSocket连接:通过
ws
或ws - client
等库建立与服务器的WebSocket连接,将绘图操作(如绘制线条的起点、终点坐标等)发送到服务器,并接收服务器广播的其他用户的绘图操作,实时更新本地Canvas。
- 服务器端:
- Node.js服务器:使用
http
模块或更便捷的express
框架搭建HTTP服务器,同时使用ws
模块创建WebSocket服务器。 - 连接管理:维护一个连接池,记录所有连接到服务器的WebSocket客户端。当有新用户连接时,将其加入连接池;用户断开连接时,从连接池中移除。
- 消息处理:接收客户端发送的绘图操作消息,然后将这些消息广播给除发送者之外的其他所有客户端,实现实时同步。
- Node.js服务器:使用
关键技术点
- WebSocket:
- 建立连接:在客户端使用
new WebSocket(url)
建立与服务器的连接,服务器端使用ws.Server
监听特定端口。例如:// 客户端 const socket = new WebSocket('ws://localhost:8080'); // 服务器端 const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 });
- 消息发送与接收:客户端使用
socket.send(data)
发送消息,通过socket.on('message', function (data) {... })
接收消息;服务器端使用ws.send(data)
向单个客户端发送消息,通过ws.on('message', function (data) {... })
接收客户端消息。
- 建立连接:在客户端使用
- 绘图操作处理:
- 在客户端:监听Canvas的
mousedown
、mousemove
、mouseup
等事件,记录绘图的起始点、移动轨迹和结束点等信息,将这些信息封装成消息格式(如JSON)发送给服务器。例如:canvas.addEventListener('mousedown', function (e) { startX = e.offsetX; startY = e.offsetY; }); canvas.addEventListener('mousemove', function (e) { if (isDrawing) { const message = { type: 'drawLine', startX: startX, startY: startY, endX: e.offsetX, endY: e.offsetY }; socket.send(JSON.stringify(message)); startX = e.offsetX; startY = e.offsetY; } }); canvas.addEventListener('mouseup', function () { isDrawing = false; });
- 在服务器端:接收到客户端发送的绘图消息后,将其广播给其他客户端。例如:
wss.on('connection', function connection(ws) { ws.on('message', function incoming(message) { wss.clients.forEach(function each(client) { if (client!== ws && client.readyState === WebSocket.OPEN) { client.send(message); } }); }); });
- 在客户端:监听Canvas的
- 处理网络延迟和丢包:
- 心跳机制:客户端和服务器端定期发送心跳消息(如每10秒一次),以检测连接是否正常。如果服务器端在一定时间内(如30秒)未收到客户端的心跳消息,则认为客户端已断开连接,将其从连接池中移除。
- 消息重传:客户端在发送消息后启动一个定时器,如果在一定时间内(如500毫秒)未收到服务器的确认消息,则重新发送该消息。服务器端在收到重复消息时,应进行去重处理,避免重复绘制。
- 缓冲机制:在客户端设置一个消息缓冲区,当网络延迟较高时,先将绘图操作消息缓存在本地,待网络恢复正常后再批量发送给服务器,减少丢包影响。在服务器端也可以设置类似的缓冲区,对广播消息进行缓冲处理,确保消息按顺序发送给客户端。