JavaScript你画我猜案例| 青训营

145 阅读3分钟

用Socket.io库实现“你画我猜”

效果图

ezgif.com-video-to-gif

WebSocket原理

WebSocket 是一种全双工通信协议,它通过在客户端和服务器之间建立持久连接,实现了实时双向通信。WebSocket 使用了 HTTP 协议进行握手,并在建立连接后切换到了自定义的协议。

  1. 客户端发起 WebSocket 握手请求:客户端通过发送 HTTP 请求给服务器,请求包含了一些特定的标头信息,如 Upgrade: websocketConnection: Upgrade。客户端还会生成一个随机的密钥 Sec-WebSocket-Key,用于后续的握手验证
  2. 服务器回应 WebSocket 握手请求:服务器接收到客户端的请求后,会检查请求是否符合 WebSocket 协议的标准,如果符合,则返回一个 HTTP 101 切换协议的响应,表示接受 WebSocket 连接。响应包含了一些特定的标头信息,如 Upgrade: websocketConnection: Upgrade,以及 Sec-WebSocket-Accept 标头来验证握手请求的有效性。
  3. 客户端与服务器建立 WebSocket 连接:如果服务器返回了正确的响应,表明握手成功,客户端和服务器之间就建立了持久的连接,此时后续的通信将不再依赖于 HTTP 协议。
  4. 双向通信:一旦 WebSocket 连接建立,客户端和服务器就可以随时互相发送消息。WebSocket 发送和接收的消息都是以二进制或文本的形式传输,可以通过 JavaScript API 中的 WebSocket 对象进行操作。

需要注意的是,WebSocket 是基于 TCP 的,因此它是一个面向连接的协议。与传统的 HTTP 请求不同,WebSocket 连接是持久的,双方可以随时进行双向通信,这对于实时应用程序非常有用,如聊天应用、实时游戏、股票报价等。

WebSocket 的优点包括实时性高、性能高效、双向通信、减少了带宽和服务器负载等。同时,它也提供了一些安全性机制,如握手请求的验证和使用加密连接等来保护通信的安全性。

image-20230726102240035

还有一种长轮询的实现实时通信的技术,它通过客户端向服务器发送一个持久的请求,服务器在有新数据可返回时才会响应请求。类似于vue监听数据watch,socket.io某些浏览器不支持的情况下,会采用长轮询来实现实时通信

代码实现

  • 安装所需依赖
npm i express socket.io
-or-
yarn add express socket.io
  • 创建服务
//初始化服务
import http from 'http';
import express from 'express';
import { Server } from 'socket.io';
import {resolve} from 'path';

//服务代理
const app = express();
const server = http.createServer(app);
const io = new Server(server);
  • 建立服务端口
// 通过index.html访问
app.get('/', (req, res) => {
    res.sendFile(resolve('./')+'/index.html');
    }
);
// 处理连接
// 监听创建服务
io.on('connection', (socket) => {
    // 监听画笔服务
    socket.on('message', (msg) => {
        io.emit('message', msg);
    });
    // 监听清除服务
    socket.on('clear', ()=> {
        io.emit('clear');
    });
});

server.listen(3000, () => {
    console.log('http://127.0.0.1:3000');
});
  • 前端JS

可以把代码抽象成三个部分:获取DOM元素、本地画图、发送数据

初始化:获取DOM元素

// 引入服务
<script src="/socket.io/socket.io.js"></script>
    <script>
        const socket = io()
        // 获取元素
        const canvas = document.querySelector('canvas')
        const cls = document.querySelector('#cls')
        const btn = document.querySelectorAll('.color')
        const input = document.querySelector('#text')
    </script>

本地画图

获取鼠标的位置,并携带默认色彩与下笔状态参数

// 画笔
const ctx = canvas.getContext('2d')
const msg = {
    color: 'black',
    isDown: false,
}
// 选择颜色
btn.forEach(item => {
	item.onclick = () => {
		msg.color = item.innerHTML
    	ctx.strokeStyle = item.innerHTML
	}
})
// 点击画线
canvas.onmousedown = e => {
	msg.isDown = true
	msg.x = e.offsetX
	msg.y = e.offsetY
	socket.emit('message', msg)
    document.onmousemove = ev => {
		msg.isDown = false
		msg.x = ev.offsetX
		msg.y = ev.offsetY
		socket.emit('message', msg)
    }
}
// 取消画线
canvas.onmouseup = () => {
     document.onmousemove = null
}

发送数据

emit -- 发送 on -- 接收

// 清空画布
cls.onclick = () => {
   // 广播清空
   socket.emit('clear')
   // 本地清空
   ctx.clearRect(0, 0, canvas.width, canvas.height)
}
// 监听广播
socket.on('message', data => {
   const { isDown, x, y, color } = data
   if (isDown) {
   ctx.beginPath()
   ctx.moveTo(x, y)
} else {
   ctx.lineTo(x, y)
   // 填充颜色
   ctx.strokeStyle = color
   ctx.stroke()
}
})
// 监听清除
socket.on('clear', () => {
   ctx.clearRect(0, 0, canvas.width, canvas.height)
})
// 监听颜色
socket.on('color', data => {
   ctx.strokeStyle = data
})
// 连接
socket.on('connect', () => {
   alert('连接成功')
   socket.emit('connect')
})
// 断开
socket.on('disconnect', () => {
   alert('离开了一名玩家')
   socket.emit('disconnect')
})