Heim >Web-Frontend >js-Tutorial >Erstellen und implementieren Sie eine Chat-Anwendung mit Socket.io und Redis.

Erstellen und implementieren Sie eine Chat-Anwendung mit Socket.io und Redis.

王林
王林Original
2024-08-26 21:40:32540Durchsuche

Build and deploy a chat application using Socket.io and Redis.

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.

Was werden wir bauen?

Wir werden eine Chat-Anwendung erstellen. Um es kurz zu machen: Wir richten nur den Server ein. Sie können Ihr eigenes Frontend-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.

Richten Sie einen Socket-Server ein.

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}`));

Redis einrichten.

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 initialisieren wir unseren Redis-Client und verbinden ihn über die Verbindungs-URL, die wir erhalten haben, mit unserem Redis-Server.

/** /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)

Umgang mit Ereignissen.

Benutzer können Räume erstellen oder vorhandenen 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 Platz zu schaffen. Wenn wir einen Raum erstellen, fügen wir dem Raum auch das Mitglied hinzu, das die Erstellung beantragt 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 vorhanden ist.

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)
  })
}

Einsatz.

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.

Machen

Einfache Bereitstellung beim Rendern. 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"]

Google Cloud Run

Wenn Sie eine kostenlose Alternative zum Rendern und Vermeiden von Kaltstarts wünschen, sollten Sie Google Cloud Run verwenden. Die Schritte zur Bereitstellung in der Cloud gehen über den Rahmen dieses Artikels hinaus, aber hier ist eine kurze Liste der Dinge, die Sie tun müssen.

  1. Erstellen Sie Ihr Docker-Image aus der unten bereitgestellten Docker-Datei.

  2. Erstellen Sie ein Artefakt-Repository mit dem Google Artifact Registry-Dienst.

  3. Benennen Sie Ihr Docker-Image in das Format -docker.pkg.dev//: um

  4. Übertragen Sie Ihr Bild in Ihr Artefakt-Repository.

  5. Stellen Sie das Image in Google Cloud Run bereit. Stellen Sie sicher, dass die Mindestanzahl aktiver Instanzen auf eins festgelegt ist, um Kaltstarts zu vermeiden.

Das ist alles für dieses Tutorial.

Danke fürs Lesen ❣️

Das obige ist der detaillierte Inhalt vonErstellen und implementieren Sie eine Chat-Anwendung mit Socket.io und Redis.. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn