TypeScript 中的 WebSocket 入门

如何开始使用 Typescript 和 React 中的 WebSockets 创建一个简单的聊天应用程序 

 示例源码:ws 

下一篇:https://blog.csdn.net/hefeng_aspnet/article/details/148898147

介绍

        WebSocket 是一项我目前还没有在工作中使用过的技术,但我知道它是一款值得了解的实用工具。我写这篇文章的目的是积累一些在简单的聊天应用中实现 WebSocket 的经验,以便将来能够在更复杂的产品中使用它们。

什么是 WebSocket?

        WebSockets 是一种行业标准方式,允许客户端和服务器实时交换消息,而无需刷新页面或轮询更改。

        它们通常用于同时向大量接收者广播相同的数据(消息),支持流式传输实时比分更新、发送交通更新、分发通知或新闻警报以及传输实时财务信息(如股票报价和市场更新)等用例。

        2011 年 12 月,互联网工程任务组 (IETF) 标准化了 WebSocket 协议,目前所有现代浏览器都支持该协议。MDN对WebSocket 协议的描述如下:

        WebSocket API是一项先进的技术,它能够在用户浏览器和服务器之间建立双向交互式通信会话。使用此 API,您可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器以获取回复。

项目计划

        计划是使用 WebSocket 服务器开发一个简单的聊天应用,该应用可以接受来自多个客户端的连接。它会接收来自客户端的新消息,然后将这些消息广播给当前连接到它的所有客户端。

        还想将消息保存到数据库中,以便用户在加载页面时可以看到历史消息,以及从 WebSocket 收到的新消息。为了实现这一点,我的服务器应用需要两个函数:

    1、WebSocket 服务器用于接受新消息并将其广播给连接的客户端

    2、允许客户端获取现有消息的 HTTP 服务器

当新消息到达服务器时,我计划将消息保存到数据库,然后通过 WebSocket 广播到连接的客户端。

项目设置

        在工作中经常使用项目,yarn workspaces但从未从零开始为个人项目创建过。我想借此机会尝试一下,在一个“monorepo”中创建两个包:一个用于客户端,另一个用于服务器。

        在后端,决定使用 .io 包ws。我知道我可以使用Socket.io来实现同样的功能,但据我了解,ws它更轻量级,因此也更简单,非常适合我的简单项目。关于 WebSocket 工具的优缺点,已经有很多文章进行了探讨,我发现这篇文章很有帮助。

        如果我要实现更复杂的功能,我想我会花时间使用Socket.io,但对于这个项目,我的目标是了解 WebSocket 的基础知识。为此,我希望选择一个不太抽象或复杂的软件包,因为我觉得这意味着我会更多地学习框架而不是底层技术。

        Socket.io有一个客户端版本,但在后端isomorphic-ws使用时似乎使用是最好的选择。ws

        NoSQL 数据库可能是消息应用程序的更好选择,但由于我熟悉它,并且复杂程度较低,因此我决定使用 Postgres 和简单的messages表进行存储。

        该项目(示例源码:ws)用 编写typescript并使用prettier,eslint以便nodemon于开发。

设置 WebSocket 服务器

首先,我需要创建简单的 HTTP 服务器,以允许用户获取历史消息,并定义获取新消息并将其插入数据库的方法。

我使用express并创建了一个端点来获取所有现有消息。然后,在我的消息存储库文件中,我创建了两个方法 - 一个用于getMessages,另一个用于insertMessage。然后,该服务器监听端口 4000。

import express, { Request, Response } from 'express';
import { getAllMessages } from './messages/messages.controller';
import { Pool } from 'pg';
import cors from 'cors';
import { Message, insertMessage } from './messages/messages.repository';

const app = express();
const db = new Pool();

app.use(express.json());
app.use(cors());

app.get('/messages', async (_: Request, res: Response) => {
  const messages = await getAllMessages({ db });
  res.send(messages);
});

const start = (): void => {
  try {
    app.listen(4000, () => console.log('Server started on port 4000'));
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

void start();

index.ts接下来,我使用 ws文档作为指南,将 WebSocket 服务器添加到我的文件中。

import { WebSocketServer, WebSocket } from 'ws';

const wss = new WebSocketServer({ port: 8080 });
// HTTP server setup goes here

const start = (): void => {
  try {
    app.listen(4000, () => console.log('Server started on port 4000'));

    wss.on('connection', (ws) => {
      ws.on('error', console.error);

      ws.on('message', (msg, isBinary) => {
        const msgAsString = msg.toString('utf-8');
        const msgObject = JSON.parse(msgAsString) as Message;
        insertMessage(msgObject, { db }).catch((e) => console.error(e));

        wss.clients.forEach((client) => {
          if (client.readyState === WebSocket.OPEN) {
            client.send(msgAsString, { binary: isBinary });
          }
        });
      });
    });

    wss.on('close', () => console.log('Connection closed'));
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

wss.on('close', () => console.log('Connection closed'));

void start();

这里,WebSocket 服务器 ( wss) 被实例化并设置为 8080 端口。连接后,我们会监听错误和消息。在ws.on(’message’…函数中,我们获取消息(以字符串形式发送的对象)并进行解析,以便读取其中的各个组成部分。

该insertMessage函数会先将其保存到数据库,然后再通过forEach循环将其广播给每个连接的客户端。我惊讶地发现,WebSocket 服务器的广播功能其实可以归结为一个简单的 for 循环!

创建客户端

接下来,我需要一种让用户与 WebSocket 服务器交互并输入和查看消息的方式。

在客户端,我使用了包,并且 WebSocket 设置在我的文件isomorphic-ws中如下所示:index.ts

import WebSocket from 'isomorphic-ws';

export const ws = new WebSocket('ws://localhost:8080/');

ws.onopen = () => console.log('WebSocket connected');
ws.onclose = () => console.log('WebSocket disconnected');

该变量ws被导出,然后在我们需要与其交互的组件中导入。

它在Form组件中用于提交表单。createMessage此处的函数设置了消息id、userId时间戳createdAt。

import { ws } from './index';

export const Form = ({ userId }: { userId: string }) => {
  const [input, setInput] = useState('');

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!input) return;
    const messageToSend = createMessage(input, userId);
    
    ws.send(JSON.stringify(messageToSend));
    
    setInput('');
  };

  return ({/* the form */});
};

ws.send将消息对象(作为字符串)传输到监听端口 8080 的服务器。

接下来,我们需要显示消息。为此,我创建了一个MessageList组件,它会在加载时从我在服务器上设置的 HTTP 端点获取数据库中的所有消息。

之后,我们监听onmessage来自 WebSocket 的事件——这是client.send当 WebSocket 向连接的客户端广播时,for 循环调用的另一端。我们将新消息从字符串解析为Message对象,并将其添加到 messages 数组的末尾。

因此,历史消息在加载(或刷新)时来自数据库,任何新消息都会通过 WebSocket 连接立即显示。

其中和子组件上有一些样式Message,但您可以在 GitHub 存储库中查看这些详细信息。

import { useEffect, useState } from 'react';
import { ws } from './index';
import { Message } from './message';

export interface Message {
  readonly id: string;
  readonly content: string;
  readonly created: string;
  readonly userId: string;
}

export const MessageList = ({ userId }: { userId: string }) => {
  const [messages, setMessages] = useState<Message[]>([]);

  useEffect(() => {
    async function getAllMessages() {
      const res = await fetch(`http://localhost:4000/messages`);
      if (!res.ok) throw new Error(res.statusText);
      const response = (await res.json()) as Message[];
      setMessages(response);
    }

    getAllMessages().catch((e) => console.log(e));
  }, []);

  ws.onmessage = (e) => {
    const msgObject = JSON.parse(e.data as string) as Message;
    setMessages([...messages, msgObject]);
  };

  return (
    <List>
      {messages.map((message) => (
        <Message message={message} myUserId={userId} />
      ))}
    </List>
  );
};

整合起来

同时运行服务器和客户端后,我们可以访问localhost:3000并查看前端界面。打开多个窗口将创建多个与 WebSocket 的连接。

前端会检查userId消息中的 是否是分配给该客户端的 。如果是,则将消息显示为Me: …;如果不是,则显示为Them:…。我本可以在用户、身份验证和样式方面做得更多,使其更加完善,但这并不是项目的真正目的。

挑战

        最初,尝试只从服务器获取新消息,并将它们分散到 React 状态中已有的消息之上。我发现这会导致“过多重新渲染”的问题,所以我决定让它在刷新/加载时获取数据库中的所有消息。虽然这种方式无法扩展,但目前为止已经达到了目的。

        在开始写代码之前,我了解到 WebSocket 有时会断开连接,所以最终可能会出现服务器不知道客户端是否断开连接,而客户端也不知道服务器是否断开连接的情况。我在开发过程中确实经常遇到这种情况,感觉 WebSocket 连接相当“脆弱”。

下一步

        为了解决 WebSocket 连接断开的问题,建议设置“心跳”,让服务器和客户端互相 ping 一下,检查它们是否仍然连接。我决定不在这个项目中实现这个功能,但这个功能肯定会是下一个要实现的,因为它会对用户体验的稳定性产生很大的影响。

        读到过关于 WebSocket 无法保证消息传递的文章。我猜你可以让服务器每次都返回一个确认消息已收到的确认,这样一来,客户端每次收到来自服务器的消息时也必须返回一个确认——但这会使双向流量翻倍。这也是Socket.io的一个原因,它似乎既能保证消息的传递,又能保证消息的顺序(文档)。同样,Socket.io为 WebSocket 提供了一套更完善的工具,ws你可以自由地使用它来实现自己的解决方案。

        还想在用户方面做更多改进——最初允许用户设置用户名,或许还可以设置头像,然后在其他人的消息中查看他们的信息,这样就能清楚地知道他们来自哪里。我还想实现不同的聊天“房间”,这样人们就可以选择他们想发送消息的群组。

总结

        很高兴接触了一些 WebSocket 的基础知识,并且学到了很多关于它们工作原理的知识。凭借我现有的基础知识,我想以后我会尝试使用Socket.io,ws并利用它更强大的功能。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/88899.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/88899.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TMS汽车热管理系统HILRCP解决方案

TMS汽车热管理系统介绍 随着汽车电动化和智能化的发展&#xff0c;整车能量管理内容增多&#xff0c;对汽车能量管理的要求也越来越高&#xff0c;从整车层面出发对各子系统进行能量统筹管理将成为电动汽车未来的发展趋势&#xff0c;其中汽车热管理是整车能量管理的重要组成部…

CCleaner Pro v6.29.11342 绿色便携版

CCleaner Pro v6.29.11342 绿色便携版 CCleaner是Piriform&#xff08;梨子公司&#xff09;最著名广受好评的系统清理优化及隐私保护软件&#xff0c;也是该公司主打和首发产品&#xff0c;它体积小、扫描速度快&#xff0c;具有强大的自定义清理规则扩展能力。CCleaner是一款…

不做手机控APP:戒掉手机瘾,找回专注与自律

在当今数字化时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。然而&#xff0c;过度依赖手机不仅会分散我们的注意力&#xff0c;影响学习和工作效率&#xff0c;还可能对身心健康造成负面影响。为了帮助用户摆脱手机依赖&#xff0c;重拾自律和专注&#xff0c;一款…

Go 语言中的接口

1、接口与鸭子类型 在 Go 语言中&#xff0c;接口&#xff08;interface&#xff09;是一个核心且至关重要的概念。它为构建灵活、可扩展的软件提供了坚实的基础。要深入理解 Go 的接口&#xff0c;我们必须首先了解一个在动态语言中非常普遍的设计哲学——鸭子类型&#xff0…

在项目中如何巧妙使用缓存

缓存 对于经常访问的数据&#xff0c;每次都从数据库&#xff08;硬盘&#xff09;中获取是比较慢&#xff0c;可以利用性能更高的存储来提高系统响应速度&#xff0c;俗称缓存 。合理使用缓存可以显著降低数据库的压力、提高系统性能。 那么&#xff0c;什么样的数据适合缓存…

SLAM中的非线性优化-2D图优化之零空间(十五)

这节在进行讲解SLAM中一个重要概念&#xff0c;零空间&#xff0c;讲它有啥用呢&#xff1f;因为SLAM中零空间的存在&#xff0c;才需要FEJ或固定约束存在&#xff0c;本节内容不属于2D图优化独有&#xff0c;先看看什么是零空间概念&#xff1b;零空间是一个核心概念&#xff…

如何解决本地DNS解析失败问题?以连接AWS ElastiCache Redis为例

在云服务开发中,DNS解析问题常常成为困扰开发者的隐形障碍。本文将通过AWS ElastiCache Redis连接失败的实际案例,详细介绍如何诊断和解决DNS解析问题,帮助你快速恢复服务连接。 引言 在使用 telnet 或 redis-cli 连接 AWS ElastiCache Redis 时,有时会遇到类似以下错误:…

探索钉钉生态中的宜搭:创建与分享应用的新视界

在当今快速发展的数字化时代&#xff0c;企业对于高效协作和信息管理的需求日益增长。作为阿里巴巴集团旗下的智能工作平台&#xff0c;钉钉不仅为企业提供了强大的沟通工具&#xff0c;其开放的生态系统也为用户带来了无限可能。其中&#xff0c;宜搭&#xff08;YiDa&#xf…

深入理解事务和MVCC

文章目录 事务定义并发事务代码实现 MVCC定义核心机制 事务 定义 什么是事务&#xff1f; 事务是指一组操作要么全部成功&#xff0c;要么全部失败的执行单位。 在数据库中&#xff0c;一个事务通常包含一组SQL语句&#xff0c;系统保证这些语句作为一个整体执行。 为什么引…

用 Python 绘制精美雷达图:多维度材料属性对比可视化全指南

&#x1f31f; 为什么选择雷达图&#xff1f;从材料科学到多维数据对比的可视化利器 在科研和数据分析领域&#xff0c;当我们需要同时展示多个维度的数据对比时&#xff0c;传统的柱状图或折线图往往显得力不从心。这时候&#xff0c;雷达图&#xff08;Radar Chart&#xff…

Excel学习03

超级表与图表 Excel中具有超级表的功能。所谓超级表&#xff08;官方名称为“表格”&#xff0c;快捷键CtrlT&#xff09;是Excel中一个强大的数据管理工具&#xff0c;它将普通的数据区域转换为具有只能功能的交互式表格。 这就是表格变为超级表的样子。超级表默认具备冻结窗…

Netflix 网飞的架构演进过程、Java在网飞中的应用|图解

写在前面 上一篇文章中&#xff0c;我们讲解了网飞当前的架构&#xff0c;但网飞的架构并不是一开始就是这样的&#xff0c;而是不断演进发展才是当前的样子。 这篇文章我们就来讲讲网飞架构的演进过程。 第一阶段&#xff1a;Zuul Gateway REST API 使用 Zuul 作为API网关…

使用ros2服务实现人脸检测2-人脸检测功能实现(适合0基础小白)

文章目录 一、用到的库二、使用步骤1.引入库2.获取图片真实路径3.检测人脸4.绘制人脸5.显示结果6.更改setup.py7.完整代码 三、结果展示 一、用到的库 face_recognition&#xff1a;实现在图片中检测人脸。 cv2&#xff1a;显示图片&#xff0c;并且可以在图像中展示检测结果。…

中国农村统计年鉴-Excel版(1985-2024年)

《中国农村统计年鉴》系统收录了全国和各省农村社会经济统计数据&#xff0c;以及近年全国农村主要统计数据&#xff0c;是一部全面反映我国农村社会经济情况的资料性年刊。年鉴内容覆盖农村人口结构、农业产值、主要农产品产量、市场物价、进出口贸易以及收入消费水平等社会经…

golang pprof性能调试工具

简介 pprof是性能调试工具,可以生成类似火焰图、堆栈图,内存分析图等。 整个分析的过程分为两步:1. 导出数据,2. 分析数据。

PPIO × 302.AI:三分钟搭建可共享的聊天机器人

最近&#xff0c;各主流模型厂商频频发布新模型&#xff0c;有一如既往强大的DeepSeek-R1-0528&#xff0c;擅长长输入推理的MiniMax-M1-80k…… 好用的AI大模型这么多&#xff0c;如何才能集成在一个应用自由使用呢&#xff1f;302.AI作为企业级AI应用平台支持各主流模型调用&…

怎么样在自己的网站/独立站中添加视频?

文章目录 **前言** 一、视频在网站/独立站的好处二、视频嵌入网站的原理三、如何简易地把视频嵌入到独立站中&#xff1f; 前言 在信息传播形式日益多元化的当下&#xff0c;静态文字与图片早已无法满足用户对沉浸式浏览体验的需求。视频以其动态画面、声音及文字相结合的特性…

【图像处理基石】什么是摄影的数码味?

“数码味”是一个摄影术语&#xff0c;通常指照片看起来不自然&#xff0c;有过度处理的痕迹&#xff0c;比如色彩过于鲜艳、对比度偏高、高光过曝、阴影死黑&#xff0c;或者有明显的锐化痕迹和噪点。这种现象在手机摄影中尤为常见&#xff0c;因为手机相机的自动算法往往会为…

报表控件stimulsoft教程:在报表、仪表板和 PDF 表单自动生成缩略图

了解缩略图的工作原理在使用Stimulsoft Demo、Stimulsoft Server和Stimulsoft Cloud时非常有用。例如&#xff0c;您可以在此处查看缩略图的实际效果 - 当侧边栏折叠时&#xff0c;将显示缩略图而不是资源列表。在本文中&#xff0c;我们将探讨 Stimulsoft 产品中报表、仪表板和…

变分自编码器(VAE)

1. 从自编码器&#xff08;AE&#xff09;到变分自编码器&#xff08;VAE&#xff09; 自编码器&#xff08;AutoEncoder, AE&#xff09; 基本结构: 自编码器是一种无监督学习模型&#xff0c;通常由两个部分组成&#xff1a; 编码器&#xff08;Encoder&#xff09;&…