Socket.io 是现代 web 开发中非常常用的一种技术,它能够在浏览器和服务器之间实现实时双向通信,被广泛用于多人在线游戏、聊天室等场景。在本文中,我们将利用 Socket.io 技术来实现一个多人在线编辑器,让多个用户可以同时编辑同一个文本文件。本教程将详细介绍如何实现这个项目,包括服务器端和客户端的实现细节,并提供完整的示例代码。
技术栈
本项目将采用以下技术栈:
- Node.js:一种基于 Chrome V8 引擎的 JavaScript 运行环境,用于编写服务器端代码。
- Express:一个流行的 Node.js Web 框架,用于创建服务器。
- Socket.io:一种实现实时双向通信的 JavaScript 库,用于实现客户端和服务器端之间的通信。
- CodeMirror:一种基于 JavaScript 的代码编辑器库,用于在浏览器中实现代码编辑器。
服务器端实现
我们将使用 Node.js 和 Express 框架来创建服务器,并使用 Socket.io 库来实现实时通信。首先,我们需要使用 npm 安装这些依赖库:
npm install express socket.io
接下来,我们需要创建一个名为 index.js 的文件,该文件将包含我们的服务器端代码。在 index.js 文件的开头,我们将引入所需的依赖库:
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');接着,我们需要创建一个 Express 实例,并使用它来创建一个 http 服务器:
const app = express(); const server = http.createServer(app);
然后,我们需要使用 Socket.io 监听服务器的连接事件,并在连接成功后保存每个客户端的 Socket 对象:
// www.javascriptcn.com code example
const io = socketIO(server);
const clients = new Map();
io.on('connection', (socket) => {
console.log(`New client connected: ${socket.id}`);
// 将当前 Socket 对象添加到 clients Map 中
clients.set(socket.id, socket);
// 处理 disconnect 事件
socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
// 从 clients Map 中移除当前 Socket 对象
clients.delete(socket.id);
});
});现在,我们已经成功创建了服务器,并监听了客户端的连接事件。每当有新的客户端连接到服务器时,我们将创建一个新的 Socket 对象,并将其保存在 clients Map 中。当客户端断开连接时,我们将从 clients Map 中移除其对应的 Socket 对象。
下一步,我们需要添加一些逻辑来处理客户端发送的事件。在本项目中,我们将向服务器发送以下两种事件:
- join:触发该事件时,客户端将发送一个 username 参数。服务器将在 clients Map 中保存该用户的用户名,并向其他客户端广播用户加入的消息。
- codeUpdate:触发该事件时,客户端将发送一个新的代码内容。服务器将保存该内容,并向所有其他客户端广播代码更新的消息。
在 handleJoinEvent 函数中,我们将处理 join 事件。在该函数中,我们将从客户端发送的数据(即 username 参数)中获取用户名,并将其保存在 clients Map 中。然后,我们将向所有其他客户端广播用户加入的消息。
// www.javascriptcn.com code example
function handleJoinEvent(socket, { username }) {
socket.username = username;
clients.set(socket.id, socket);
console.log(`${username} joined the room`);
socket.broadcast.emit('userJoined', {
username: socket.username,
numUsers: clients.size,
});
}在 handleCodeUpdateEvent 函数中,我们将处理 codeUpdate 事件。在该函数中,我们将从客户端发送的数据(即代码内容)中获取代码,并将其保存在代码编辑器中。然后,我们将向所有其他客户端广播代码更新的消息。
function handleCodeUpdateEvent(socket, { code }) {
console.log(`${socket.username} updated the code`);
socket.broadcast.emit('codeUpdated', {
code,
username: socket.username,
});
}下面是完整的服务器端代码示例:
// www.javascriptcn.com code example
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIO(server);
const PORT = process.env.PORT || 3000;
const clients = new Map();
app.use(express.static(__dirname + '/public'));
io.on('connection', (socket) => {
console.log(`New client connected: ${socket.id}`);
socket.on('join', (data) => {
handleJoinEvent(socket, data);
});
socket.on('codeUpdate', (data) => {
handleCodeUpdateEvent(socket, data);
});
socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
clients.delete(socket.id);
});
function handleJoinEvent(socket, { username }) {
socket.username = username;
clients.set(socket.id, socket);
console.log(`${username} joined the room`);
socket.broadcast.emit('userJoined', {
username: socket.username,
numUsers: clients.size,
});
}
function handleCodeUpdateEvent(socket, { code }) {
console.log(`${socket.username} updated the code`);
socket.broadcast.emit('codeUpdated', {
code,
username: socket.username,
});
}
});
server.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});客户端实现
在客户端,我们将创建一个基于 CodeMirror 的代码编辑器,并使用 Socket.io 和服务器进行双向通信。首先,我们需要在 HTML 文件中添加以下代码,以引入所需的依赖库:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/theme/monokai.min.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/mode/javascript/javascript.min.js"></script> <script src="/socket.io/socket.io.js"></script>
在客户端代码中,我们需要创建一个连接到服务器的 Socket 对象,并在连接成功后发送 join 事件来加入服务器。在该事件的回调函数中,我们将获取服务器返回的 numUsers 参数,并更新 UI 以显示在线用户的数量。
// www.javascriptcn.com code example
const socket = io.connect();
const username = prompt('Enter your username:');
socket.emit('join', { username });
socket.on('userJoined', ({ username, numUsers }) => {
console.log(`${username} joined the room. There are now ${numUsers} users online.`);
});
document.addEventListener('DOMContentLoaded', () => {
const editor = CodeMirror(document.querySelector('.editor'), {
mode: 'javascript',
theme: 'monokai',
});
editor.on('change', () => {
socket.emit('codeUpdate', { code: editor.getValue() });
});
socket.on('codeUpdated', ({ code, username }) => {
if (username !== socket.username) {
editor.setValue(code);
}
});
});在以上代码中,我们还创建了一个 CodeMirror 的编辑器实例,并在其 onChange 事件上发送 codeUpdate 事件来通知服务器代码已更新。当服务器向所有客户端广播代码更新消息时,我们将更新本地的 CodeMirror 编辑器实例。
完整代码示例
以下是完整的示例代码,包括服务器端和客户端的实现细节:
index.js:
// www.javascriptcn.com code example
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIO(server);
const PORT = process.env.PORT || 3000;
const clients = new Map();
app.use(express.static(__dirname + '/public'));
io.on('connection', (socket) => {
console.log(`New client connected: ${socket.id}`);
socket.on('join', (data) => {
handleJoinEvent(socket, data);
});
socket.on('codeUpdate', (data) => {
handleCodeUpdateEvent(socket, data);
});
socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
clients.delete(socket.id);
});
function handleJoinEvent(socket, { username }) {
socket.username = username;
clients.set(socket.id, socket);
console.log(`${username} joined the room`);
socket.broadcast.emit('userJoined', {
username: socket.username,
numUsers: clients.size,
});
}
function handleCodeUpdateEvent(socket, { code }) {
console.log(`${socket.username} updated the code`);
socket.broadcast.emit('codeUpdated', {
code,
username: socket.username,
});
}
});
server.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});public/index.html:
// www.javascriptcn.com code example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Socket.io Code Editor</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/theme/monokai.min.css" />
</head>
<body>
<h1>Socket.io Code Editor</h1>
<div class="editor"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/mode/javascript/javascript.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io.connect();
const username = prompt('Enter your username:');
socket.emit('join', { username });
socket.on('userJoined', ({ username, numUsers }) => {
console.log(`${username} joined the room. There are now ${numUsers} users online.`);
});
document.addEventListener('DOMContentLoaded', () => {
const editor = CodeMirror(document.querySelector('.editor'), {
mode: 'javascript',
theme: 'monokai',
});
editor.on('change', () => {
socket.emit('codeUpdate', { code: editor.getValue() });
});
socket.on('codeUpdated', ({ code, username }) => {
if (username !== socket.username) {
editor.setValue(code);
}
});
});
</script>
</body>
</html>运行以上代码示例后,即可在浏览器中打开多个窗口来模拟多个用户同时编辑同一个代码文件的场景。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67d024d3e46428fe9ecc9f15