WebSocket 和 Socket.io 是现代网络应用开发中经常使用的技术,它们能够快速建立客户端和服务器之间的双向通信连接,支持实时推送和处理大量的数据。
本文将介绍如何基于 WebSocket 和 Socket.io 实现一个简单的在线聊天室,并提供完整的代码示例和详细的开发指导,内容适合于前端工程师和初学者进行学习和实践。
前置知识
在阅读本文之前,需要掌握以下知识:
- 前端开发基础知识,如 HTML、CSS 和 JavaScript;
- Node.js 的基础知识,并且已经安装并配置好开发环境;
- Express 框架的基础知识,本文中使用 Express 作为后端框架。
准备工作
首先,我们需要创建一个新的 Node.js 项目,并在项目目录下安装 Express 和 Socket.io 依赖。
mkdir chatroom cd chatroom npm init -y npm install express socket.io
然后,在项目目录下创建 public
和 views
目录,并在 public
目录下创建 js
、css
和 img
子目录,用于存放前端资源文件。
mkdir public views cd public mkdir js css img
在 views
目录下创建 index.html
文件,用于编写前端页面。
// javascriptcn.com 代码示例 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>聊天室</title> <link rel="stylesheet" href="/css/style.css" /> </head> <body> <div id="chat"> <ul id="messages"></ul> <form id="form"> <input id="message-input" autocomplete="off" /> <button id="send-button">发送</button> </form> </div> <script src="/socket.io/socket.io.js"></script> <script src="/js/client.js"></script> </body> </html>
在 public/css
目录下创建 style.css
文件,编写前端样式。
// javascriptcn.com 代码示例 * { padding: 0; margin: 0; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } body { background-color: #f6f6f6; } #chat { margin: 30px auto; width: 500px; background-color: #fff; box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); border-radius: 10px; overflow: hidden; } #messages { list-style: none; margin: 0; padding: 20px; max-height: 300px; overflow-y: auto; } #messages li { padding: 10px; border-bottom: 1px solid #eee; } #messages li:last-child { border-bottom: none; } #form { display: flex; align-items: center; justify-content: center; padding: 20px; } #message-input { flex: 1; padding: 10px; border-radius: 5px; border: none; margin-right: 10px; } #send-button { padding: 10px; border-radius: 5px; border: none; background-color: #3f51b5; color: #fff; cursor: pointer; }
在 public/js
目录下创建 client.js
文件,编写前端 JavaScript 代码。
// javascriptcn.com 代码示例 const socket = io(); const messagesList = document.querySelector("#messages"); const messageInput = document.querySelector("#message-input"); const sendButton = document.querySelector("#send-button"); function sendMessage(message) { socket.emit("chat message", message); } function addMessage(message) { const item = document.createElement("li"); item.textContent = message; messagesList.appendChild(item); } sendButton.addEventListener("click", (event) => { event.preventDefault(); if (messageInput.value) { sendMessage(messageInput.value); addMessage("我:" + messageInput.value); messageInput.value = ""; } }); socket.on("chat message", (message) => { addMessage("对方:" + message); });
接着,我们在项目目录下创建 app.js
文件,作为服务器入口文件。
// javascriptcn.com 代码示例 const express = require("express"); const app = express(); const http = require("http").createServer(app); const io = require("socket.io")(http); app.use(express.static("public")); app.get("/", (req, res) => { res.sendFile(__dirname + "/views/index.html"); }); io.on("connection", (socket) => { socket.on("chat message", (message) => { io.emit("chat message", message); }); }); http.listen(3000, () => { console.log("listening on *:3000"); });
最后,我们可以在命令行中启动项目并访问 http://localhost:3000
查看效果了。
node app.js
开发实战
在上面的准备工作中,我们已经通过 Express 和 Socket.io 创建了一个简单的在线聊天室。接下来,我们将对这个聊天室进行功能扩展和优化。
用户名和房间号
在聊天室中,用户可能需要输入用户名和房间号来加入一个特定的聊天室,这就需要前端能够向服务器发送一些特定的信息。我们可以在前端页面中添加一个表单,让用户输入这些信息,并修改前端 JavaScript 代码。
<form id="form"> <input id="username-input" type="text" placeholder="用户名" /> <input id="room-input" type="text" placeholder="房间号" /> <input id="message-input" type="text" placeholder="请输入消息" autocomplete="off" /> <button id="send-button">发送</button> </form>
// javascriptcn.com 代码示例 const socket = io(); const messagesList = document.querySelector("#messages"); const usernameInput = document.querySelector("#username-input"); const roomInput = document.querySelector("#room-input"); const messageInput = document.querySelector("#message-input"); const sendButton = document.querySelector("#send-button"); function sendMessage(message) { socket.emit("chat message", message); } function addMessage(message) { const item = document.createElement("li"); item.textContent = message; messagesList.appendChild(item); } sendButton.addEventListener("click", (event) => { event.preventDefault(); if (messageInput.value) { sendMessage({ username: usernameInput.value || '匿名用户', room: roomInput.value || '默认房间', message: messageInput.value }); addMessage(usernameInput.value + ":" + messageInput.value); messageInput.value = ""; } }); socket.on("chat message", (data) => { if (data.room === (roomInput.value || '默认房间')) { addMessage(data.username + ":" + data.message); } });
修改前端代码之后,我们还需要修改服务器代码来处理新的信息类型。在服务器端,我们需要为每个连接维护一个用户名和所在房间的信息,并在收到聊天消息时发送给所有连接,让它们进行处理。
// javascriptcn.com 代码示例 const express = require("express"); const app = express(); const http = require("http").createServer(app); const io = require("socket.io")(http); app.use(express.static("public")); app.get("/", (req, res) => { res.sendFile(__dirname + "/views/index.html"); }); const users = {}; io.on("connection", (socket) => { socket.on("chat message", (data) => { const { room, message, username } = data; if (!users[socket.id]) { users[socket.id] = { username, room }; socket.join(room); } io.in(room).emit("chat message", { username, message, room }); }); socket.on("disconnect", () => { if (users[socket.id]) { const { room } = users[socket.id]; delete users[socket.id]; io.in(room).emit("system message", { message: "用户已离开聊天室", room, }); } }); }); http.listen(3000, () => { console.log("listening on *:3000"); });
在服务器代码中,我们维护了一个 users
对象,用于保存每个连接对应的用户名和房间号。当收到聊天消息时,我们通过 socket.id
获取当前连接的信息,并将其加入到对应的房间中,然后将消息发送给该房间中的所有连接。
系统提示信息
在聊天室中,一些系统事件发生时可能需要向用户发送一些提示信息,比如当一个用户加入或离开聊天室时。我们可以在服务器端发送这些提示信息,让前端通过特定的事件来处理这些信息。
// javascriptcn.com 代码示例 const socket = io(); // ... socket.on("system message", (data) => { if (data.room === (roomInput.value || '默认房间')) { addMessage(`系统提示:${data.message}`); } });
// javascriptcn.com 代码示例 io.on("connection", (socket) => { socket.on("chat message", (data) => { // ... }); socket.on("disconnect", () => { if (users[socket.id]) { const { room, username } = users[socket.id]; delete users[socket.id]; io.in(room).emit("system message", { message: `${username}已离开聊天室`, room, }); } }); const { room } = socket.handshake.query; socket.join(room); socket.emit("system message", { message: "欢迎来到聊天室", room }); });
在服务器端,我们在连接成功时就加入了对应的房间,并向该房间中的所有连接发送一条欢迎信息。当一个连接离开时,我们也向该房间中的所有连接发送一条系统消息,提示其谁离开了聊天室。
名称不重复
在聊天室中,每个人都应该有独立的名称,因此我们需要在用户名重复时进行处理。我们可以在服务器代码中保存所有已使用的用户名,并在接收到聊天消息时进行验证,如果发现当前用户名已经存在,就让用户重新选择一个新的用户名。
// javascriptcn.com 代码示例 const express = require("express"); const app = express(); const http = require("http").createServer(app); const io = require("socket.io")(http); app.use(express.static("public")); app.get("/", (req, res) => { res.sendFile(__dirname + "/views/index.html"); }); const users = {}; function checkUsername(username) { for (const id in users) { if (users[id].username === username) { return false; } } return true; } io.on("connection", (socket) => { socket.on("chat message", (data) => { const { room, message, username } = data; if (!users[socket.id]) { if (!checkUsername(username)) { socket.emit("system message", { message: "用户名已被占用,请重新输入", room, }); return; } users[socket.id] = { username, room }; socket.join(room); } io.to(room).emit("chat message", { username, message, room }); }); // ... }); http.listen(3000, () => { console.log("listening on *:3000"); });
在服务器代码中,我们定义了 checkUsername
函数,用于检查当前用户名是否已经被使用。在接收到聊天消息时,我们首先判断当前连接是否已经有了用户名信息,如果没有,则检查用户名是否被占用,如果没有被占用就将其保存,并加入到对应的房间中,否则就发送一条系统消息,提示用户重新选择用户名。
总结
本文详细介绍了基于 WebSocket 和 Socket.io 实现的在线聊天室的开发实战,并提供了完整的代码示例和详细的开发指导。通过阅读本文,读者可以掌握如何使用 Socket.io 实现一个实时通信的应用,了解如何处理多种事件、发送多种消息,并学习如何优化应用性能和用户体验。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6582e5a5d2f5e1655ddf5107