23、RxJS 中的冷热可观察对象与 WebSocket 应用

RxJS 中的冷热可观察对象与 WebSocket 应用

1. 冷热可观察对象简介

可观察函数是一种惰性计算,意味着在观察者订阅之前,包含可观察对象声明的整个操作符序列不会开始执行。但在订阅之前发生的事件会怎样呢?比如鼠标移动可观察对象对所有鼠标事件的处理,或者接收到一组消息的 WebSocket 会如何处理这些消息?这些潜在的重要事件会丢失吗?实际上,这些活跃的数据源不会等待订阅者开始监听才开始发出事件,因此了解如何处理这种情况至关重要。

RxJS 将可观察源分为两类:热可观察对象和冷可观察对象。这一分类不仅决定了订阅语义的行为特征,还影响着流的整个生命周期。可观察对象的“温度”也会影响流和生产者是一起管理还是分开管理,这对资源利用有很大影响。

1.1 冷可观察对象

冷可观察对象在观察者订阅之前不会开始发出所有值。它通常用于包装有界数据类型(如数字、数字范围、字符串、数组和 HTTP 请求)以及无界类型(如生成器函数)。这些资源是被动的,因为它们的声明与执行是独立的,这意味着这些可观察对象在创建和执行时是真正惰性的。

每次新的订阅都会创建一个新的独立流,每个订阅者都会从开始独立地接收完全相同的一组事件。可以将创建冷可观察对象看作是创建一个稍后执行的计划或食谱,每次执行都是从头到尾重复进行。只有当你选择开始“烹饪”时,冷可观察对象才会开始发出事件。

从纯函数式编程(FP)的角度来看,冷可观察对象的行为很像函数。函数可以被视为一个惰性或待计算的值,只有在需要时调用才会返回结果。同样,可观察对象在订阅之前不会运行,你可以使用提供的观察者来处理其返回值。

冷可观察对象的声明通常以静态操作符(如 of() from() )开始,定时可观察对象 interval() timer() 也表现为冷可观察对象。以下是一个简单的示例:

const arr$ = Rx.Observable.from([1,2,3,4,5,6,7,8,9]);
const sub1 = arr$.subscribe(console.log);
// ... 片刻之后 ... //
const sub2 = arr$.subscribe(console.log);
const evenNumbers$ = Rx.Observable.fromArray(numbersArr)
    .filter(num => num % 2 === 0);
evenNumbers$.subscribe(num => {
    // 在这里使用偶数
});

对于冷可观察对象,所有订阅者无论何时订阅,都会观察到相同的事件。例如 interval() 操作符,每次新的订阅都会创建一个全新的间隔实例。以下代码展示了如何使用同一个可观察对象的两个订阅者分别监听偶数和奇数:

const interval$ = Rx.Observable.interval(500);
const isEven = x => x % 2 === 0;
interval$
  .filter(isEven)
  .take(5)
  .subscribe(x => {
    console.log(`Even number found: ${x}`);
   });
interval$
  .filter(R.compose(R.not, isEven))
  .take(5)
  .subscribe(x => {
    console.log(`Odd number found: ${x}`);
   });

可以用以下流程图来展示冷可观察对象的订阅情况:

graph LR
    A[冷可观察对象 interval$] --> B[订阅者 1]
    A --> C[订阅者 2]
    B --> D(接收事件 1,2,3,4,5...)
    C --> E(接收事件 1,2,3,4,5...)

冷可观察对象的定义为:当被订阅时,会向任何活跃的订阅者发出整个事件序列。

1.2 热可观察对象

流并不总是在你想要的时候开始,也不能期望总是从每个可观察对象中获取每个事件。有时,通过延迟订阅,你可能会故意避免某些事件,就像隐式的 skip() 操作。

热可观察对象无论是否有订阅者都会产生事件,它们是活跃的。在现实世界中,热可观察对象用于模拟点击、鼠标移动、触摸等事件,或通过事件发射器暴露的任何其他事件。与冷可观察对象不同,热可观察对象的订阅者通常只接收订阅创建后发出的事件。

热可观察对象在没有订阅者时仍然是惰性的,事件只是发出并被忽略。只有当观察者订阅流时,操作符管道才会开始工作,数据才会向下游流动。这种类型的流对许多开发者来说更直观,因为它与他们熟悉的 Promise 和事件发射器的行为非常相似。

从理论上讲,由于热可观察对象发出的数据具有不可预测和不可重复的性质,它们并不完全是纯的。毕竟,对外部刺激(如按钮点击)的反应可以被视为一种依赖于其他资源(如 DOM 或时间)行为的副作用。但从应用和代码的角度来看,所有可观察对象都可以被视为纯的。

与冷可观察对象为每个订阅者创建独立的数据源副本不同,热可观察对象将相同的订阅共享给所有监听它的观察者。因此,热可观察对象在被订阅时,会从订阅点开始发出正在进行的事件序列,而不是从开始。

可观察对象是热还是冷部分取决于它所包装的源的类型。例如,任何鼠标事件处理程序的可观察对象通常是热的,因为它只是对现有 addEventListener() 调用的抽象,其行为取决于系统对鼠标事件的处理。而包装静态数据源(如数组)或使用生成数据(通过生成器函数)的可观察对象通常是冷的,因为它们在没有订阅者监听时不会开始产生值。

以下是热可观察对象订阅情况的流程图:

graph LR
    A[热可观察对象 mouseMove$] --> B[订阅者 1]
    A --> C[订阅者 2]
    B --> D(从订阅点接收事件)
    C --> E(从订阅点接收事件)

热可观察对象的定义为:无论是否有订阅者都会产生事件的可观察对象。

一般来说,尽可能使用冷可观察对象更好,因为它们本质上是无状态的,每个订阅都是独立的,从 RxJS 内部角度来看,需要担心的共享状态更少。

2. 新的数据来源:WebSocket

时间在可观察对象中无处不在,特别是在热可观察对象中。数据源开始发出事件的时间与订阅者开始监听的时间之间的差异可能会导致问题。例如,当你在节目进行中切换到喜欢的电视节目时,可能会错过一些开头的情节。同样,在使用 WebSocket 或其他事件发射器的简单消息系统中,错过任何消息都可能对应用程序的正常运行至关重要。如果在关键消息包到达后才订阅热可观察对象,那些指令可能会丢失。

2.1 WebSocket 简介

除了绑定到 DOM 事件和 AJAX 调用外,RxJS 还可以轻松绑定到 WebSocket。WebSocket(WS)是一种异步通信协议,与传统 HTTP 相比,它为客户端到服务器提供了更快、更高效的通信线路。这对于实时聊天、流媒体服务或游戏等高度交互的应用程序非常有用。

WebSocket 运行在 TCP 连接之上,其优势在于可以在保持连接打开的情况下来回传递信息(利用浏览器的多路复用功能和保持活动特性),并且服务器可以在浏览器没有明确请求的情况下向其发送内容。

WebSocket 通信始于握手,这将 HTTP 世界与 WebSocket 连接起来。在这个过程中,会讨论连接和安全的细节,为双方之间的安全、高效通信铺平道路。具体步骤如下:
1. 在双方之间建立套接字连接以进行初始握手。
2. 将通信协议从常规 HTTP 切换或升级到基于套接字的协议。
3. 双向发送消息(全双工)。
4. 由服务器或客户端发起断开连接。

以下是 WebSocket 通信的流程图:

graph LR
    A[客户端] --> B[握手]
    B --> C[协议升级]
    C --> D[双向消息传递]
    D --> E[断开连接]
    F[服务器] --> B

这个过程的关键是初始握手,它协商升级过程。由于 WebSocket 使用与 HTTP 和 HTTPS 相同的端口(分别为 80 和 443,WebSocket Secure 协议使用 443),通过相同端口路由请求是有利的,因为防火墙通常配置为允许这些信息自由流动。从异步事件消息传递的角度来看,可以将 WebSocket 视为客户端 - 服务器通信的事件发射器。

2.2 Node.js 中的简单 WebSocket 服务器

为了演示,我们将使用 Node.js 编写一个简单的 WebSocket 服务器,但你也可以使用其他喜欢的平台(如 Python、PHP、Java 或任何具有套接字 API 的平台)。服务器将是一个简单的 TCP 应用程序,使用 Node.js WebSocket API 监听端口 1337(任意选择)。WebSocket 与 HTTP 服务器协商,作为发送和接收消息的工具。一旦服务器收到请求,它将回复消息“Hello Socket”。以下是代码示例:

const Rx = require('rxjs/Rx');
const WebSocketServer = require('websocket').server;
const http = require('http');
// ws port
const server = http.createServer();
server.listen(1337); 
// create the server
wsServer = new WebSocketServer({
    httpServer: server
});

上述代码中,首先导入了 RxJS 核心 API、WebSocket 服务器库和 HTTP 库,然后创建了一个 HTTP 服务器并让其监听 1337 端口,最后创建了 WebSocket 服务器并将其与 HTTP 服务器关联起来。

3. 使用 RxJS 处理 WebSocket 消息流

在了解了 WebSocket 的基本原理和如何创建简单的 WebSocket 服务器后,接下来我们将探讨如何使用 RxJS 来处理 WebSocket 的消息流。这可以帮助我们更好地管理异步消息,避免错过重要信息。

3.1 创建 WebSocket 可观察对象

我们可以将 WebSocket 封装成一个可观察对象,这样就可以利用 RxJS 的操作符来处理消息。以下是一个简单的示例:

const Rx = require('rxjs/Rx');
const WebSocket = require('ws');

function createWebSocketObservable(url) {
    return Rx.Observable.create(observer => {
        const socket = new WebSocket(url);

        socket.on('open', () => {
            observer.next('WebSocket 连接已打开');
        });

        socket.on('message', (message) => {
            observer.next(message);
        });

        socket.on('close', () => {
            observer.complete();
        });

        socket.on('error', (error) => {
            observer.error(error);
        });

        return () => {
            if (socket.readyState === WebSocket.OPEN) {
                socket.close();
            }
        };
    });
}

const webSocket$ = createWebSocketObservable('ws://localhost:1337');

webSocket$.subscribe(
    (message) => console.log('收到消息:', message),
    (error) => console.error('发生错误:', error),
    () => console.log('WebSocket 连接已关闭')
);

在上述代码中,我们定义了一个 createWebSocketObservable 函数,它接受一个 WebSocket 的 URL 作为参数,并返回一个可观察对象。在可观察对象的创建过程中,我们监听了 WebSocket 的 open message close error 事件,并分别调用观察者的 next complete error 方法。同时,我们还返回了一个清理函数,用于在订阅取消时关闭 WebSocket 连接。

3.2 处理 WebSocket 消息的操作符

有了 WebSocket 可观察对象后,我们可以使用 RxJS 的操作符来处理消息。例如,我们可以使用 map 操作符将接收到的消息进行转换,使用 filter 操作符过滤掉不需要的消息。以下是一个示例:

webSocket$
  .map((message) => JSON.parse(message))
  .filter((data) => data.type === 'important')
  .subscribe(
    (data) => console.log('收到重要消息:', data),
    (error) => console.error('发生错误:', error),
    () => console.log('WebSocket 连接已关闭')
  );

在这个示例中,我们使用 map 操作符将接收到的消息解析为 JSON 对象,然后使用 filter 操作符过滤出类型为 important 的消息。最后,我们订阅处理后的可观察对象,只处理重要消息。

3.3 分享 WebSocket 可观察对象

有时候,我们可能需要多个订阅者来处理同一个 WebSocket 连接的消息。为了避免为每个订阅者创建独立的 WebSocket 连接,我们可以使用 share 操作符来分享可观察对象。以下是一个示例:

const sharedWebSocket$ = webSocket$.share();

sharedWebSocket$.subscribe(
    (message) => console.log('订阅者 1 收到消息:', message),
    (error) => console.error('订阅者 1 发生错误:', error),
    () => console.log('订阅者 1: WebSocket 连接已关闭')
);

sharedWebSocket$.subscribe(
    (message) => console.log('订阅者 2 收到消息:', message),
    (error) => console.error('订阅者 2 发生错误:', error),
    () => console.log('订阅者 2: WebSocket 连接已关闭')
);

在这个示例中,我们使用 share 操作符将 webSocket$ 可观察对象转换为一个共享的可观察对象 sharedWebSocket$ 。这样,多个订阅者就可以共享同一个 WebSocket 连接,避免了资源的浪费。

以下是使用 share 操作符后 WebSocket 可观察对象的订阅情况表格:
| 订阅者 | 接收消息情况 |
| ---- | ---- |
| 订阅者 1 | 共享 WebSocket 连接,接收消息 |
| 订阅者 2 | 共享 WebSocket 连接,接收消息 |

4. 冷热可观察对象与 WebSocket 的综合应用

在实际应用中,我们可能会同时遇到冷热可观察对象和 WebSocket 的情况。以下是一些综合应用的场景和处理方法。

4.1 冷可观察对象与 WebSocket 结合

当我们需要在 WebSocket 连接建立后执行一些初始化操作时,可以使用冷可观察对象。例如,我们可以在 WebSocket 连接打开后,从服务器获取一些初始数据。以下是一个示例:

const initData$ = Rx.Observable.of([1, 2, 3, 4, 5]);

webSocket$.take(1).subscribe(() => {
    initData$.subscribe(
        (data) => console.log('初始化数据:', data),
        (error) => console.error('获取初始化数据时发生错误:', error),
        () => console.log('初始化数据获取完成')
    );
});

在这个示例中,我们创建了一个冷可观察对象 initData$ ,它包含一些初始数据。当 WebSocket 连接打开后,我们订阅 initData$ 并获取初始化数据。

4.2 热可观察对象与 WebSocket 结合

WebSocket 本身可以看作是一个热可观察对象,因为它无论是否有订阅者都会接收和发送消息。我们可以利用热可观察对象的特性,让多个订阅者共享 WebSocket 的消息流。例如,我们可以在一个页面上有多个组件都需要接收 WebSocket 的消息,这时就可以使用热可观察对象来实现。

以下是一个简单的流程图,展示了热可观察对象与 WebSocket 结合的应用:

graph LR
    A[WebSocket 服务器] --> B[WebSocket 连接]
    B --> C[热可观察对象 WebSocket$]
    C --> D[订阅者 1]
    C --> E[订阅者 2]
    D --> F(处理消息)
    E --> G(处理消息)

4.3 处理消息丢失问题

在使用热可观察对象的 WebSocket 时,可能会遇到消息丢失的问题。为了避免这种情况,我们可以使用一些 RxJS 的操作符,如 replay shareReplay 。以下是一个使用 shareReplay 的示例:

const replayWebSocket$ = webSocket$.shareReplay(1);

replayWebSocket$.subscribe(
    (message) => console.log('订阅者 1 收到消息:', message),
    (error) => console.error('订阅者 1 发生错误:', error),
    () => console.log('订阅者 1: WebSocket 连接已关闭')
);

setTimeout(() => {
    replayWebSocket$.subscribe(
        (message) => console.log('订阅者 2 收到消息:', message),
        (error) => console.error('订阅者 2 发生错误:', error),
        () => console.log('订阅者 2: WebSocket 连接已关闭')
    );
}, 5000);

在这个示例中,我们使用 shareReplay(1) 操作符将 WebSocket 可观察对象转换为一个可以重放最后一个消息的共享可观察对象。这样,即使订阅者 2 在订阅时已经错过了一些消息,它仍然可以接收到最后一个消息。

通过以上的综合应用,我们可以更好地利用冷热可观察对象和 WebSocket 的特性,处理各种复杂的异步消息场景。

内容概要:本文介绍了一个基于MATLAB实现的无人机三维路径规划项目,采用蚁群算法(ACO)多层感知机(MLP)相结合的混合模型(ACO-MLP)。该模型通过三维环境离散化建模,利用ACO进行全局路径搜索,并引入MLP对环境特征进行自适应学习启发因子优化,实现路径的动态调整多目标优化。项目解决了高维空间建模、动态障碍规避、局部最优陷阱、算法实时性及多目标权衡等关键技术难题,结合并行计算参数自适应机制,提升了路径规划的智能性、安全性和工程适用性。文中提供了详细的模型架构、核心算法流程及MATLAB代码示例,涵盖空间建模、信息素更新、MLP训练融合优化等关键步骤。; 适合人群:具备一定MATLAB编程基础,熟悉智能优化算法神经网络的高校学生、科研人员及从事无人机路径规划相关工作的工程师;适合从事智能无人系统、自动驾驶、机器人导航等领域的研究人员; 使用场景及目标:①应用于复杂三维环境下的无人机路径规划,如城市物流、灾害救援、军事侦察等场景;②实现飞行安全、能耗优化、路径平滑实时避障等多目标协同优化;③为智能无人系统的自主决策环境适应能力提供算法支持; 阅读建议:此资源结合理论模型MATLAB实践,建议读者在理解ACOMLP基本原理的基础上,结合代码示例进行仿真调试,重点关注ACO-MLP融合机制、多目标优化函数设计及参数自适应策略的实现,以深入掌握混合智能算法在工程中的应用方法。
【51系列微控制器简介】 51系列微控制器属于嵌入式控制单元,源自Intel公司早期开发的8051架构,因其集成度高、成本低廉且易于上手,在各类电子装置中普遍采用。该芯片内部通常包含中央处理器、随机存取存储器、只读存储器、定时计数单元以及多组并行输入输出接口,能够自主执行数据运算设备调控功能。 【心形彩灯动态显示方案】 利用51系列微控制器实现的心形彩灯动态显示方案,是将微电子控制技术视觉光效设计相融合的典型实践。该方案通过微控制器对发光二极管的发光强度及闪烁时序进行精确调度,从而呈现连续变化的多彩心形光影图案。其实施过程主要涵盖以下技术环节: 1. **外围电路连接**:心形灯阵中的各色发光二极管需经由适配的驱动电路微控制器的通用输入输出引脚相连,每个发光单元可独立对应一个或多个引脚以实现分路调控。 2. **色彩合成信号输出**:全彩发光二极管多采用红绿蓝三原色混光原理,通过调整各基色通道的占空比可合成丰富色调。微控制器需分别调控各通道的脉冲宽度调制信号以生成目标色彩。 3. **控制代码开发**:采用C语言等嵌入式编程语言编写控制指令集,例如运用定时中断机制设定闪烁周期,结合循环结构逻辑判断实现动态模式切换。 4. **光效序列算法**:动态光效如渐变流水、明暗呼吸、光谱循环等需通过特定算法实现,需根据实际显示需求对时序参数进行数学建模优化。 5. **代码转化写入**:完成源代码编写后,使用专用编译工具生成机器可识别的十六进制文件,再通过在线编程接口将代码固化至微控制器的程序存储器。 6. **系统验证调整**:在实体硬件上运行程序并观测实际光效,根据显示效果对电路参数或程序逻辑进行迭代修正,确保光效符合设计规范。 7. **供电方案设计**:为保障长期稳定运行,需设计合理的电源稳压滤波电路,控制整体功耗并避免电压波动对器件造成影响。 8. **可靠性保障措施**:设计阶段需考虑电气隔离、散结构等安全要素,防止过压、过等异常情况导致系统故障。 综上所述,该心形彩灯演示方案是一项融合硬件电路构建、嵌入式软件开发、控制算法设计及系统调试的综合实践项目,对于深入理解微控制器工作原理、提升工程实现能力具有显著促进作用。通过完整实施此类项目,既可巩固微控制器基础应用技能,亦能培养系统性解决复杂技术问题的创新能力。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值