使用 Express 和 SocketIO 实现实时通信

实时通信,简称 RTC,是指几乎没有延迟的实时通信。它可用于发送消息、通知等。本文将探讨如何为两个不同用户之间的基本聊天应用设置自己的服务器。

由于本文主要围绕后台展开,因此我不会详细介绍创建前台。我将使用 SvelteKit,但它与其他框架(如 NextJS 或 NuxtJS)的集成,甚至与 vanilla JS 的集成并无太大区别。

创建 express 应用程序

首先,初始化 package.json 文件。

npm init -y

接下来,在 package.json 文件中修改/添加以下内容。

{
    "name": "server",
    "version": "1.0.0",
    "description": "",
    "main": "src/app.ts",
    "type": "module",
    "scripts": {
        "start": "nodemon --exec \"node - import ./ts-loader.js\" --experimental-specifier-resolution=node src/app.ts"
    },
    "keywords": [],
    "author": "",
    "license": "ISC"
}

安装必要的依赖项:

npm install cors dotenvexpress socket.io 
npm install --save-dev @types/node @types/express ts-node typescript nodemon

这将安装 cors 用于处理向服务器发送的跨源请求,安装 dotenv 用于在应用程序中加载环境变量,安装 express 用于编写应用程序本身,安装 socket.io 用于实现实时通信。

它还将为我们的应用程序添加必要的类型。

手动或使用 npx tsc - init 创建一个 tsconfig.json 文件,并用以下内容修改其内容。

{
    "compilerOptions": {
        "target": "es2016",
        "module": "ES6",
        "moduleResolution": "Bundler",
        "allowImportingTsExtensions": true,
        "allowJs": false,
        "noEmit": true,
        "verbatimModuleSyntax": true,
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "useUnknownInCatchVariables": false,
        "skipLibCheck": true
    },
    "include": ["./src/**/*.ts"],
    "exclude": ["./node_modules/**"]
}

现在,在 express 应用程序的根目录下添加 ts-loader.js 文件。这样我们就不会遇到 esm 和 ts 的问题了。

import { register } from 'node:module';
import { pathToFileURL } from 'node:url';

register('ts-node/esm', pathToFileURL('./'));

添加一个 .env 文件,并在其中填入要使用的端口。

FRONTEND_URL=localhost:5174
SERVER_PORT=4000

然后,定义主文件 src/app.ts

import express from "express";
import dotenv from 'dotenv';
import cors from "cors";

// Load the env variables inside our app.
dotenv.config();

// Create the express app.
const app = express();

// Authorize any requests coming from our frontend url.
app.use(cors({
    origin: process.env.FRONTEND_URL
}));

// A basic endpoint to test our app.
app.get("/", (req, res) => {
    res.status(200).send("Hello from Express !")
});

// Listen to our app at our defined port.
app.listen(process.env.SERVER_PORT, () => {
    console.log(`[Server] Running on port ${process.env.SERVER_PORT}`);
});

最后,启动应用程序并访问服务器的网址,您将看到 Express。

npm start

实现 SocketIO

现在,在服务器和前端中添加 SocketIO。

在 Express 内部

用以下内容调整 Express 应用程序。

import express from "express";
import dotenv from 'dotenv';
import cors from "cors";
import { createServer } from "http";
import { Server } from "socket.io";

// Load the env variables inside our app.
dotenv.config();

// Create the express app.
const app = express();
// Create an http server from the express app.
const server = createServer(app);
// Create an io server.
const io = new Server(server);

// Authorize any requests coming from our frontend url.
app.use(cors({
    origin: process.env.FRONTEND_URL
}));

// Fires when a new connection with the server has been created.
io.on("connection", (socket) => {
    console.log("New user connected.")

    // Fires when a connection has been severed from the server, e.g. a page reload.
    socket.on('disconnect', () => {
        console.log('A user disconnected.');
    });
});

// Listen to our app at our defined port.
server.listen(process.env.SERVER_PORT, () => {
    console.log(`[Server] Running on port ${process.env.SERVER_PORT}`);
});

我们将添加一个 HTTP 服务器,我们将在该服务器上监听传入连接和断开连接,而不是通过 Express 应用程序和 SocketIO 来监听。

前台内部

首先,安装 socket.io 的客户端。

npm install socket.io-client

接下来,创建一个 socket.ts 文件,从中导入我们的套接字。

import { Socket, io } from "socket.io-client";

/** The socket client. */
export let socket: Socket | null = null;

/** Initializes the socket connection if it isn't already set. */
export async function initSocket() {
    if (socket !== null) return;

    // Adapt the url if needed.
    const _socket = io('http://localhost:4000', {
        transports: ['websocket'],
        upgrade: false
    });

    _socket.on('connection', () => {
        console.log('Connected');
    });

    socket = _socket;
}

现在,在页面中导入 initSocket 函数,并在挂载后调用它。将其调整到您的前端。

<script lang="ts">
    import { initSocket } from "$lib/socket";
    import { onMount } from "svelte";

    onMount(async () => {
        await initSocket();
    });
</script>

<p>Our chat app</p>

现在,当你重新加载页面时,应该能在 express 应用程序中看到日志。

发送消息

最后,我们将从客户端发送一条消息,并将其传送给其他已连接的用户,这有点像群聊(注:如果要将消息发送给特定客户端,则需要存储用户Ids/会话Ids的映射,并使用to方法进行广播,但由于这更像是对 websockets 的介绍,我就不详细介绍了)。

服务器内部

io.connection 函数中和 socket.on("disconnect") 调用后,我们将有一个套接字事件来接收消息并将其广播给其他人,包括我们自己。通常,您会连接一个数据库来存储消息,因为它们会在页面重载后消失。

import express from "express";
import dotenv from 'dotenv';
import cors from "cors";
import { createServer } from "http";
import { Server } from "socket.io";

// Load the env variables inside our app.
dotenv.config();

// Create the express app.
const app = express();
// Create an http server from the express app.
const server = createServer(app);
// Create an io server.
const io = new Server(server);

// Authorize any requests coming from our frontend url.
app.use(cors({
    origin: process.env.FRONTEND_URL
}));

// Fires when a new connection with the server has been created.
io.on("connection", (socket) => {
    console.log("New user connected.")

    // Fires when a connection has been severed from the server, e.g. a page reload.
    socket.on('disconnect', () => {
        console.log('A user disconnected.');
    });

    // Receives a message and broadcasts it to everyone else, including the sender.
    socket.on("message", (message: string) => {
        io.emit("message", { sender: socket.id, message });
    });
});

// Listen to our app at our defined port.
server.listen(process.env.SERVER_PORT, () => {
    console.log(`[Server] Running on port ${process.env.SERVER_PORT}`);
});

前台内部

更新页面,以发送、接收和显示消息。

<script lang="ts">
    import { initSocket, socket } from "$lib/socket";
    import { onMount } from "svelte";

    let message: string = "";
    let messages: { sender: string, message: string }[] = [];

    onMount(async () => {
        await initSocket();

        // Someone (or us) has sent a message. We'll add it to the messages.
        socket?.on("message", (message) => {
        messages = […messages, message];
        });
    });

    async function sendMessage() {
        // Send a message to the message event.
        socket?.emit("message", message);
        message = "";
    }
</script>

<p>Our chat app</p>
<input bind:value={message} />
<button on:click={sendMessage}>Send</button>
<p>Messages</p>
{#each messages as message}
    <div>Sender : {message.sender} | Message : {message.message}</div>
{/each}

您可以打开两个不同的标签页/浏览器并发送信息进行测试。另一个也会更新!

现在,我们已经启动了自己的 websocket 服务器,准备打造下一个社交媒体!开个玩笑,无论如何,你可以用 SocketIO 做很多事情。

作者:Sajidur Rahman,源自sajidur.dev.

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/48193.html

(0)

相关推荐

发表回复

登录后才能评论