在本教程中,我们将使用 Web 套接字构建一个聊天应用程序。当您想要构建需要实时传输数据的应用程序时,Web 套接字非常有用。
在本教程结束时,您将能够设置自己的套接字服务器、实时发送和接收消息、在 Redis 中存储数据以及在渲染和 google cloud run 上部署您的应用程序。
我们将构建一个聊天应用程序。为了简单起见,我们将只设置服务器。您可以使用自己的前端框架并遵循。
在这个聊天应用程序中,会有房间,用户可以加入房间并开始聊天。为了让一切简单,我们假设用户名不是唯一的。然而,每个房间只能有一个具有特定用户名的用户。
首先我们需要安装所需的依赖项。
npm i express cors socket.io -D @types/node
我们将使用 http 模块来设置我们的套接字服务器。由于我们的应用程序将在终端中运行,因此我们必须允许所有来源。
import express from "express"; import cors from "cors" import { Server } from "socket.io"; import { createServer } from "http" const app = express(); const server = createServer(app); // create a socket server. const io = new Server(server, { cors: { origin: "*", credentials: true, } }); // listen to connections errors io.engine.on("connection_error", console.log) app.use(cors()) const PORT = 3000; server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
我们将使用 Redis 来存储我们的消息以及房间和用户信息。您可以使用 upstash redis (免费)。在 upstash 仪表板中创建一个新的 Redis 实例。创建后,您将收到一个 redis url,您可以使用它来连接到您的 redis 实例。
安装您选择的任何 Redis 客户端。我将使用 ioredis。
npm i ioredis
接下来,我们将初始化我们的redis客户端并使用我们获得的连接url将其连接到我们的redis服务器。
/** /src/index.ts */ import { Redis } from "ioredis" if (!process.env.REDIS_URL) throw new Error("REDIS_URL env variable is not set"); const redis = new Redis(process.env.REDIS_URL); // listen to connection events. redis.on("connect", () => console.log("Redis connected")) redis.on("error", console.log)
用户可以创建房间或加入现有房间。房间由唯一的房间 ID 标识。每个成员都有一个用户名,该用户名在房间内是唯一的,而不是全局的。
我们可以通过将房间 ID 存储在 Redis 集中来跟踪服务器中的所有活动房间。
出于我们的目的,用户名仅在房间内是唯一的。因此,我们将它们与房间 ID 一起存储在一个集合中。这确保了房间 ID 与会员 ID 的组合在全球范围内是唯一的。
我们可以设置套接字事件来创建房间。当我们创建房间时,我们还将请求创建房间的成员添加到房间中。
io.on("connection", () => { // ... socket.on("create:room", async (message) => { console.log("create:room", message) const doesRoomExist = await redis.sismember("rooms", message.roomId) if (doesRoomExist === 1) return socket.emit("error", { message: "Room already exist."}) const roomStatus = await redis.sadd("rooms", message.roomId) const memStatus = await redis.sadd("members", message.roomId "::" message.username) if (roomStatus === 0 || memStatus === 0) return socket.emit("error", { message: "Room creation failed." }) socket.join(message.roomId) io.sockets.in(message.roomId).emit("create:room:success", message) io.sockets.in(message.roomId).emit("add:member:success", message) }) }
要将新成员添加到现有房间,我们首先需要检查该成员是否已存在于该房间中。
io.on("connection", () => { // ... socket.on("add:member", async (message) => { console.log("add:member", message) const doesRoomExist = await redis.sismember("rooms", message.roomId) if (doesRoomExist === 0) return socket.emit("error", { message: "Room does not exist." }) const doesMemExist = await redis.sismember("members", message.roomId "::" message.username) if (doesMemExist === 1) return socket.emit("error", { message: "Username already exists, please choose another username." }) const memStatus = await redis.sadd("members", message.roomId "::" message.username) if (memStatus === 0) return socket.emit("error", { message: "User creation failed." }) socket.join(message.roomId) io.sockets.in(message.roomId).emit("add:member:success", message) }) socket.on("remove:member", async (message) => { console.log("remove:member", message) const doesRoomExist = await redis.sismember("rooms", message.roomId) if (doesRoomExist === 0) return socket.emit("error", { message: "Room does not exist." }) await redis.srem("members", message.roomId "::" message.username) socket.leave(message.roomId) io.sockets.in(message.roomId).emit("remove:member:success", message) }) }
最后,我们创建聊天事件。
io.on("connection", () => { socket.on("create:chat", (message) => { console.log("create:chat", message) redis.lpush("chat::" message.roomId, message.username "::" message.message) io.sockets.in(message.roomId).emit("create:chat:success", message) }) }
Socket 服务器需要持久连接,它在无服务器环境中无法工作。所以你不能在 vercel 中部署你的套接字服务器。
您可以将其部署在许多地方,例如 Render、fly.io 或 Google Cloud Run。
在渲染上部署很简单。如果您有 dockerfile,它将自动从该 dockerfile 构建您的项目。渲染有一个免费层,但请记住免费层中会有冷启动。
这是我的 dockerfile。
# syntax=docker/dockerfile:1 ARG NODE_VERSION=20.13.1 ARG PNPM_VERSION=9.4.0 FROM node:${NODE_VERSION}-bookworm AS base ## set shell to bash SHELL [ "/usr/bin/bash", "-c" ] WORKDIR /usr/src/app ## install pnpm. RUN --mount=type=cache,target=/root/.npm \ npm install -g pnpm@${PNPM_VERSION} # ------------ FROM base AS deps # Download dependencies as a separate step to take advantage of Docker's caching. # Leverage a cache mount to /root/.local/share/pnpm/store to speed up subsequent builds. # Leverage bind mounts to package.json and pnpm-lock.yaml to avoid having to copy them # into this layer. RUN --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --prod --frozen-lockfile # ----------- FROM deps AS build ## downloading dev dependencies. RUN --mount=type=bind,source=package.json,target=package.json \ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile COPY . . RUN pnpm run build # ------------- FROM base AS final ENV NODE_ENV=production USER node COPY package.json . # Copy the production dependencies from the deps stage and also # the built application from the build stage into the image. COPY --from=deps /usr/src/app/node_modules ./node_modules COPY --from=build /usr/src/app/dist ./dist EXPOSE 3000 ENTRYPOINT [ "pnpm" ] CMD ["run", "start"]
如果您想要一个免费的替代方案来渲染并避免冷启动,您应该使用 Google Cloud Run。在云运行上部署的步骤超出了本文的范围,但这里有您需要执行的操作的简短列表。
从下面提供的 dockerfile 构建 docker 镜像。
使用 Google Artifact Registry 服务创建工件存储库。
将 docker 镜像重命名为
将您的图像推送到您的工件存储库。
将映像部署到 Google Cloud Run。确保将最小活动实例设置为 1,以避免冷启动。
本教程就是这样。
感谢您的阅读❣️
免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。
Copyright© 2022 湘ICP备2022001581号-3