Dans ce didacticiel, nous allons créer une application de chat à l'aide de sockets Web. Les sockets Web sont très utiles lorsque vous souhaitez créer des applications nécessitant un transfert de données en temps réel.
À la fin de ce didacticiel, vous serez en mesure de configurer votre propre serveur socket, d'envoyer et de recevoir des messages en temps réel, de stocker des données dans Redis et de déployer votre application lors du rendu et de l'exécution de Google Cloud.
Nous allons créer une application de chat. Pour faire court, nous allons uniquement configurer le serveur. Vous pouvez utiliser votre propre framework front-end et suivre.
Dans cette application de chat, il y aura des salles et les utilisateurs pourront rejoindre une salle et commencer à discuter. Pour que tout reste simple, nous supposerons que les noms d’utilisateur ne sont pas uniques. Cependant, chaque salle ne peut avoir qu'un seul utilisateur avec un nom d'utilisateur spécifique.
Nous devons d'abord installer les dépendances requises.
npm i express cors socket.io -D @types/node
Nous utiliserons le module http pour configurer notre serveur socket. Puisque notre application fonctionnera dans le terminal, nous devrons autoriser toutes les origines.
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}`));
Nous utiliserons Redis pour stocker nos messages ainsi que les informations sur la salle et les utilisateurs. Vous pouvez utiliser Upstash Redis (gratuit). Créez une nouvelle instance Redis dans votre tableau de bord Upstash. Après la création, vous recevrez une URL Redis que vous pourrez utiliser pour vous connecter à votre instance Redis.
Installez n'importe quel client Redis de votre choix. J'utiliserai ioredis.
npm i ioredis
Ensuite, nous initialiserons notre client Redis et le connecterons à notre serveur Redis en utilisant l'URL de connexion que nous avons obtenue.
/** /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)
Les utilisateurs peuvent créer des salles ou rejoindre des salles existantes. Les chambres sont identifiées par des identifiants de pièce uniques. Chaque membre possède un nom d'utilisateur unique dans une salle, et non globalement.
Nous pouvons garder une trace de toutes les salles actives sur notre serveur, en stockant leurs identifiants de salle dans un ensemble Redis.
Pour notre objectif, les noms d'utilisateur ne sont uniques qu'à l'intérieur d'une pièce. Nous les stockons donc dans un ensemble avec l’identifiant de la pièce. Cela garantit que la combinaison de l'identifiant de la salle et de l'identifiant du membre est unique à l'échelle mondiale.
Nous pouvons configurer un événement de socket pour créer une salle. Lorsque nous créons une salle, nous ajoutons également le membre qui a demandé sa création dans la salle.
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) }) }
Pour ajouter un nouveau membre dans une salle existante, nous devons d'abord vérifier si le membre existe déjà dans cette salle.
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) }) }
Enfin, nous créons l'événement de chat.
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) }) }
Le serveur Socket nécessite des connexions persistantes, il ne fonctionnera pas dans des environnements sans serveur. Vous ne pouvez donc pas déployer votre serveur socket dans vercel.
Vous pouvez le déployer dans de nombreux endroits comme Render, fly.io ou Google Cloud Run.
Déploiement sur rendu simple. Si vous disposez d'un fichier docker, il construira automatiquement votre projet à partir de ce fichier docker. Le rendu a un niveau gratuit, mais gardez à l'esprit qu'il y aura un démarrage à froid dans le niveau gratuit.
Voici mon fichier docker.
# 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"]RÉP TRAVAIL /usr/src/app ## installe pnpm. EXÉCUTER --mount=type=cache,target=/root/.npm \ npm install -g pnpm@${PNPM_VERSION} # ------------ DE la base AS dépôts # Téléchargez les dépendances dans le cadre d'une étape distincte pour profiter de la mise en cache de Docker. # Tirez parti d'un montage de cache sur /root/.local/share/pnpm/store pour accélérer les builds suivantes. # Tirez parti des montages de liaison sur package.json et pnpm-lock.yaml pour éviter d'avoir à les copier # dans cette couche. EXÉCUTER --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 ## téléchargement des dépendances de développement. EXÉCUTER --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 COPIE . . RUN pnpm exécuter la construction # ------------- DE base COMME final ENV NODE_ENV=production Nœud UTILISATEUR COPIER package.json . # Copiez les dépendances de production de l'étape deps et également # l'application construite depuis l'étape de construction dans l'image. COPIER --from=deps /usr/src/app/node_modules ./node_modules COPIER --from=build /usr/src/app/dist ./dist EXPOSER 3000 POINT D'ENTRÉE
Google Cloud Exécuter
C'est tout pour ce tutoriel.
Merci d'avoir lu ❣️
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3