当前位置: 首页 > news >正文

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.lqws.cn/news/555121.html

相关文章:

  • stream使用案例
  • 【Docker基础】Docker容器管理:docker stats及其参数详解
  • JavaScript中Object()的解析与应用
  • 深入详解:决策树算法的概念、原理、实现与应用场景
  • 思维提升篇-数学抽象与计算机实现
  • ChatboxAI 搭载 GPT 与 DeepSeek,引领科研与知识库管理变革
  • 华为云Flexus+DeepSeek征文|利用华为云一键部署的Dify平台构建高效智能电商客服系统实战
  • 我的世界模组开发进阶教程——机械动力的数据生成(2)
  • 系统架构设计师论文分享-论ATAM的使用
  • nginx基本使用 linux(mac下的)
  • 计算机网络——概述
  • AI代码助手实践指南
  • Linux-读者写者问题
  • 【世纪龙科技】新能源汽车动力电池总成装调与检修教学软件
  • 如何在anaconda上创建虚拟环境--windows下
  • 大模型在急性冠脉综合征预测及诊疗方案制定中的应用研究
  • C++算法学习专题:双指针
  • (五)神经网络
  • 《Go语言高级编程》RPC 入门
  • 思科交换机接口显示inactive原因
  • c# 比较两个list 之间元素差异
  • 搭建Flink分布式集群
  • 5 BERT预训练模型
  • WPF XAML 格式化工具(XAML Styler)
  • STM32F103C8T6参数说明
  • 从单体架构到微服务:微服务架构演进与实践
  • Linux【9】-----Linux系统编程(线程池和并发socket编程 c语言)
  • 【安卓Sensor框架-2】应用注册Sensor 流程
  • 【Network Management】ComM模块中的PNState和ChannelState间的关系
  • 从【人工智能】到【计算机视觉】。深度学习引领的未来科技创新与变革