In diesem Tutorial erstellen wir eine Chat-Anwendung mithilfe von Web-Sockets. Web-Sockets sind wirklich nützlich, wenn Sie Anwendungen erstellen möchten, die eine Echtzeitübertragung von Daten erfordern.
Am Ende dieses Tutorials werden Sie in der Lage sein, Ihren eigenen Socket-Server einzurichten, Nachrichten in Echtzeit zu senden und zu empfangen, Daten in Redis zu speichern und Ihre Anwendung auf Render und Google Cloud Run bereitzustellen.
Wir werden eine Chat-Anwendung erstellen. Um es kurz zu machen: Wir werden nur den Server einrichten. Sie können Ihr eigenes Front-End-Framework verwenden und mitmachen.
In dieser Chat-Anwendung gibt es Räume und Benutzer können einem Raum beitreten und mit dem Chatten beginnen. Der Einfachheit halber gehen wir davon aus, dass die Benutzernamen nicht eindeutig sind. Allerdings kann jeder Raum nur einen Benutzer mit einem bestimmten Benutzernamen haben.
Zuerst müssen wir die erforderlichen Abhängigkeiten installieren.
npm i express cors socket.io -D @types/node
Wir werden das http-Modul verwenden, um unseren Socket-Server einzurichten. Da unsere App im Terminal ausgeführt wird, müssen wir alle Ursprünge zulassen.
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}`));
Wir werden Redis verwenden, um unsere Nachrichten zusammen mit Raum- und Benutzerinformationen zu speichern. Sie können Upstash Redis (kostenlos) verwenden. Erstellen Sie eine neue Redis-Instanz in Ihrem Upstash-Dashboard. Nach der Erstellung erhalten Sie eine Redis-URL, mit der Sie eine Verbindung zu Ihrer Redis-Instanz herstellen können.
Installieren Sie einen beliebigen Redis-Client Ihrer Wahl. Ich werde ioredis verwenden.
npm i ioredis
Als nächstes werden wir unseren Redis-Client initialisieren und ihn über die Verbindungs-URL, die wir erhalten haben, mit unserem Redis-Server verbinden.
/** /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)
Benutzer können Räume erstellen oder bestehenden Räumen beitreten. Räume werden durch eindeutige Raum-IDs identifiziert. Jedes Mitglied hat einen Benutzernamen, der innerhalb eines Raums und nicht global eindeutig ist.
Wir können alle aktiven Räume auf unserem Server verfolgen, indem wir ihre Raum-IDs in einem Redis-Set speichern.
Für unseren Zweck sind Benutzernamen nur innerhalb eines Raums eindeutig. Deshalb bewahren wir sie zusammen mit der Raum-ID in einem Set auf. Dadurch wird sichergestellt, dass die Kombination aus Raum-ID und Mitglieds-ID weltweit eindeutig ist.
Wir können ein Socket-Ereignis einrichten, um Raum zu schaffen. Wenn wir einen Raum erstellen, fügen wir dem Raum auch das Mitglied hinzu, das die Erstellung angefordert hat.
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) }) }
Um ein neues Mitglied zu einem bestehenden Raum hinzuzufügen, müssen wir zunächst prüfen, ob das Mitglied bereits in diesem Raum existiert.
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) }) }
Zuletzt erstellen wir das Chat-Event.
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-Server erfordert dauerhafte Verbindungen, er funktioniert nicht in serverlosen Umgebungen. Sie können Ihren Socket-Server also nicht in Vercel bereitstellen.
Sie können es an vielen Orten wie Render, fly.io oder Google Cloud Run bereitstellen.
Bereitstellung beim Rendern einfach. Wenn Sie über eine Docker-Datei verfügen, wird Ihr Projekt automatisch aus dieser Docker-Datei erstellt. Render hat eine kostenlose Stufe, aber bedenken Sie, dass es in der kostenlosen Stufe einen Kaltstart gibt.
Hier ist meine Docker-Datei.
# 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"]ARBEITSVERZEICHNIS /usr/src/app ## pnpm installieren. RUN --mount=type=cache,target=/root/.npm \ npm install -g pnpm@${PNPM_VERSION} # ------------ VON der Basis AS abh # Laden Sie Abhängigkeiten als separaten Schritt herunter, um das Caching von Docker zu nutzen. # Nutzen Sie einen Cache-Mount in /root/.local/share/pnpm/store, um nachfolgende Builds zu beschleunigen. # Nutzen Sie Bind-Mounts für package.json und pnpm-lock.yaml, um sie nicht kopieren zu müssen # in diese Ebene. 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 # ----------- VON abhängig AS Build ## Entwicklungsabhängigkeiten werden heruntergeladen. 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 KOPIEREN. . RUN pnpm run build # ------------- VON der Basis AS endgültig ENV NODE_ENV=Produktion USER-Knoten KOPIEREN Sie package.json . # Kopieren Sie die Produktionsabhängigkeiten aus der Deps-Phase und auch # die erstellte Anwendung von der Build-Phase in das Image. COPY --from=deps /usr/src/app/node_modules ./node_modules COPY --from=build /usr/src/app/dist ./dist 3000 aussetzen ENTRYPOINT
Google Cloud Run
Das ist alles für dieses Tutorial.
Danke fürs Lesen ❣️
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3