Angular中的WebSockets:创建Angular Service以使用Web套接字

图片
在本文中,我将尝试详细介绍Angular框架及其已不可或缺的助手-RxJs框架内的技术范围,而我们不会故意涉及服务器实现,因为 这是另一篇文章的完整主题。

对于那些已经熟悉Angular但想要直接加深他们对该主题的知识的人来说,本文将是有用的。

首先,一些基本信息。

什么是WebSocket,为什么需要它


根据Wikipedia的说法,WebSocket是一种“双工通信协议(它可以同时发送和接收),它可以通过TCP连接进行传输,旨在用于浏览器和Web服务器之间的实时消息传递。
WebSocket旨在在Web浏览器和Web服务器中实现,但可用于任何客户端或服务器应用程序。 WebSocket协议是基于TCP协议的独立协议。 它使浏览器与网站之间的交互更加紧密,从而促进了交互式内容的分发和实时应用程序的创建。”

换句话说,WebSocket允许服务器在任何期望的时间从客户端接收请求并将请求发送到客户端,因此,浏览器(客户端)和服务器在连接时具有同等的权限和交换消息的能力。 常规的AJAX请求需要传输完整的HTTP标头,这意味着双向流量都将增加,而建立连接后Web套接字的开销仅为两个字节。 Web套接字将HTTP标头中传输的信息量减少了成千上万次,并显着减少了等待时间。 Web套接字连接支持跨域(如CORS)。

在服务器端,有支持Web套接字的包,在客户端是HTML5 WebSocket API,它具有三种方法的接口:

WebSocket-连接到WebSocket服务器,然后通过该连接发送和接收数据的主界面;
CloseEvent-连接关闭时WebSocket对象调度的事件;
MessageEvent-当从服务器接收到消息时,由WebSocket调度的事件。

这是从JavaSript实现级别看的样子:

const ws = new WebSocket("ws://www.example.com/socketserver", "protocolOne"); ws.onopen = () => { ws.onmessage = (event) => { console.log(event); } ws.send("Here's some text that the server is urgently awaiting!"); }; 

onmessage-侦听来自服务器的消息
发送 -将您的消息发送到服务器

也就是说,从根本上讲,一切都非常简单,但是如果您希望深入研究该主题,则可以使用MDN Web Docs ,同时可以学习在此API之上实现自己的层的库。

为什么不害怕使用WebSocket


可能吓到的第一点是浏览器支持 。 如今已不存在此类问题-WebSocket和移动网段几乎完全支持WebSocket。

图片

图片

https://caniuse.com/#feat=websockets

第二点是易于实施 。 是的,起初这令人沮丧。

该API非常简单,乍一看可能很难理解如何使用如此少量的方法,因为除一个方法外,所有方法均报告错误或连接,并且其中仅一个方法( onmessage )携带关于使用什么网络套接字,即 从服务器接收数据。

同时,要注意的是服务器通常发送不同的数据,因此,我们需要几个不同的消息吗? 还是需要为每个数据模型创建自己的连接?

因此,任务是:您需要接受服务器上的用户模型和最新新闻模型,甚至可能还要接受其他模型。

我遇到过这样一个“优雅”的实现:

 const wsUser = new WebSocket("ws://www.example.com/user"); wsUser.onmessage = (event) => { // ... }; const wsNews = new WebSocket("ws://www.example.com/news"); wsNews.onmessage = (event) => { // ... }; const wsTime = new WebSocket("ws://www.example.com/time"); wsTime.onmessage = (event) => { // ... }; const wsDinner = new WebSocket("ws://www.example.com/dinner"); wsDinner.onmessage = (event) => { // ... }; const wsCurrency = new WebSocket("ws://www.example.com/currency"); wsCurrency.onmessage = (event) => { // ... }; const wsOnline = new WebSocket("ws://www.example.com/online"); wsOnline.onmessage = (event) => { // ... }; const wsLogin = new WebSocket("ws://www.example.com/login"); wsLogin.onmessage = (event) => { // ... }; const wsLogout = new WebSocket("ws://www.example.com/logout"); wsLogout.onmessage = (event) => { // ... }; 

乍一看,一切都是合乎逻辑的。 但是现在想象一下,如果有数十个或数百个它们,它将是什么样子。 在我碰巧从事的一个项目中,大约有三百个事件。

图片

我们解决了这个问题。

所有用于Web套接字的第三方库都允许您订阅addEventListener类型的消息。 看起来像这样:

 ws.on("user", (userData) => { / .. }) 

众所周知,我们可以使用一种方法onmessage进行操作, onmessage接收所有数据作为其连接的一部分,因此此代码看起来有些不寻常。 它的实现方式如下: onmessage返回一个包含数据字段的MessageEvent 。 这些数据包含服务器发送给我们的信息。 该对象如下所示:

 { "event": "user", "data": { "name": "John Doe", ... } } 

其中, 事件是确定服务器发送哪些信息的关键。 接下来,在前端侧,创建一条总线,该总线按事件过滤信息并将其发送到所需的地址:

 const ws = new WebSocket("ws://www.example.com"); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.event === 'user') { // ... } if (data.event === 'news') { // ... } }; 


这样就可以在同一连接中接收不同的数据,并通过类似于JS事件通常的语法来订阅它们。

Angular中的WebSockets


最后,我们了解了最重要的事情-直接在Angular中使用WebSockets。

尽管使用本机WebSocket API非常简单,但是在本文中,我们将使用RxJ,这当然是因为我们在谈论Angular。

可以在Angular应用程序中使用本机WebSocket API,创建易于使用的界面,可观察到RxJs,订阅必要的消息等,但是RxJs已经为您完成了主要工作:WebSocketSubject是标准WebSocket的反应式包装API 它不会创建事件总线或重新连接处理。 这是一个常规主题,您可以使用它来使用反应式Web套接字。

RxJs WebSocket主题


因此, WebSocketSubject需要WebSocketSubjectConfig和一个可选目标,您可以在其中传递指向观察到的Subject的链接,创建一个Observable,通过它可以侦听和发送Web套接字的消息。

简而言之,将连接URL作为参数传递给WebSocketSubject,并以RxJ的常规方式订阅Web套接字的所有活动。 并且,如果您需要向服务器发送消息,请使用相同的常用方法webSocketSubject.next(数据)。

我们提供使用WebSocket Angular的服务


简要描述我们对服务的期望:

  • 统一简洁的界面;
  • 能够在DI依赖关系的连接级别进行配置
  • 重用的可能性;
  • 打字;
  • 通过密钥订阅接收信息的能力;
  • 能够终止订阅;
  • 向服务器发送消息;
  • 重新连接。

最后一点值得特别注意。 当使用Web套接字时,重新连接或重新连接到服务器的组织是至关重要的因素,因为 网络中断,服务器崩溃或其他导致连接中断的错误可能导致应用程序崩溃。

重要的是要注意,重新连接尝试不应太频繁且不应无限期地继续,例如 此行为可以挂起客户端。

让我们开始吧。

首先,我们将创建一个服务配置接口和一个模块,该模块将提供在连接时进行配置的功能。

如果可能,我将缩短代码,即您可以在GitHub上的俄语Angular社区中看到的完整版本。

 export interface WebSocketConfig { url: string; reconnectInterval?: number; reconnectAttempts?: number; } export class WebsocketModule { public static config(wsConfig: WebSocketConfig): ModuleWithProviders { return { ngModule: WebsocketModule, providers: [{ provide: config, useValue: wsConfig }] }; } } 

接下来,我们需要描述Web套接字消息的接口:

 export interface IWsMessage<T> { event: string; data: T; } 

其中event是键,而由键获取的数据是类型化模型。

服务的公共接口如下所示:

 export interface IWebsocketService { on<T>(event: string): Observable<T>; send(event: string, data: any): void; status: Observable<boolean>; } 

服务包含以下字段:

 //   WebSocketSubject private config: WebSocketSubjectConfig<IWsMessage<any>>; private websocketSub: SubscriptionLike; private statusSub: SubscriptionLike; // Observable    interval private reconnection$: Observable<number>; private websocket$: WebSocketSubject<IWsMessage<any>>; // ,      private connection$: Observer<boolean>; //  Observable       private wsMessages$: Subject<IWsMessage<any>>; //       private reconnectInterval: number; //    private reconnectAttempts: number; //      private isConnected: boolean; //   public status: Observable<boolean>; 

在服务类的构造函数中,我们获取连接模块时指定的WebSocketConfig对象:

 constructor(@Inject(config) private wsConfig: WebSocketConfig) { this.wsMessages$ = new Subject<IWsMessage<any>>(); //  ,  ,     this.reconnectInterval = wsConfig.reconnectInterval || 5000; this.reconnectAttempts = wsConfig.reconnectAttempts || 10; //      connection$   websocket$ this.config = { url: wsConfig.url, closeObserver: { next: (event: CloseEvent) => { this.websocket$ = null; this.connection$.next(false); } }, //     connection$ openObserver: { next: (event: Event) => { console.log('WebSocket connected!'); this.connection$.next(true); } } }; // connection status this.status = new Observable<boolean>((observer) => { this.connection$ = observer; }).pipe(share(), distinctUntilChanged()); //      this.statusSub = this.status .subscribe((isConnected) => { this.isConnected = isConnected; if (!this.reconnection$ && typeof(isConnected) === 'boolean' && !isConnected) { this.reconnect(); } }); // ,  -    this.websocketSub = this.wsMessages$.subscribe( null, (error: ErrorEvent) => console.error('WebSocket error!', error) ); //  this.connect(); } 

连接方法本身很简单:

 private connect(): void { this.websocket$ = new WebSocketSubject(this.config); //  //   ,    , //  ,  // ,    this.websocket$.subscribe( (message) => this.wsMessages$.next(message), (error: Event) => { if (!this.websocket$) { // run reconnect if errors this.reconnect(); } }); } 

重新连接有点复杂:

 private reconnect(): void { //  interval    reconnectInterval this.reconnection$ = interval(this.reconnectInterval) .pipe(takeWhile((v, index) => index < this.reconnectAttempts && !this.websocket$)); //     ,        this.reconnection$.subscribe( () => this.connect(), null, () => { // Subject complete if reconnect attemts ending this.reconnection$ = null; if (!this.websocket$) { this.wsMessages$.complete(); this.connection$.complete(); } }); } 

on方法也非常简单,甚至没有什么可评论的。

 public on<T>(event: string): Observable<T> { if (event) { return this.wsMessages$.pipe( filter((message: IWsMessage<T>) => message.event === event), map((message: IWsMessage<T>) => message.data) ); } } 

send方法更简单:

 public send(event: string, data: any = {}): void { if (event && this.isConnected) { //   any ,   ""   string //      :) this.websocket$.next(<any>JSON.stringify({ event, data })); } else { console.error('Send error!'); } } 

这就是整个服务。 如您所见,大部分代码都属于重新连接的组织。

现在让我们看看如何使用它。 连接WebsocketModule模块:

 imports: [ WebsocketModule.config({ url: environment.ws //      'ws://www.example.com' }) ] 

在组件构造函数中,我们注入服务并订阅来自“ messages ”的消息 ,然后将文本发送回服务器:

 constructor(private wsService: WebsocketService) { this.wsService.on<IMessage[]>('messages') .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send('text', 'Test Text!'); }); } 

事件的名称更方便放入常量或枚举。 我们在websocket.events.ts文件的某个位置创建并将其写入其中:

 export const WS = { ON: { MESSAGES: 'messages' }, SEND: { TEXT: 'text' } }; 

使用创建的WS对象重写订阅:

 this.wsService.on<IMessage[]>(WS.ON.MESSAGES) .subscribe((messages: IMessage[]) => { console.log(messages); this.wsService.send(WS.SEND.TEXT, 'Test Text!'); }); 

图片

总结


实际上,仅此而已。 这是Angular开发人员需要了解的WebSockets的最低要求。 我希望我已经很清楚地涵盖了这个话题。 该服务的完整版本可以在GitHub找到

对于所有问题,您都可以在评论中联系,无论是通过Telegram还是在同一个地方的Angular频道上,可以与联系。

Source: https://habr.com/ru/post/zh-CN416155/


All Articles