EventSource之重连特性 学习

        进入本文博客正题之前,或者对EventSource完全还没了解之前,可以简单阅读一下下面这篇博客https://blog.csdn.net/qq_44327851/article/details/135157086

        通过对EventSource的简单学习之后,我们很容易就能发现EventSource其中一个特性就是——自动重连,其中它还提供了一个retry字段来设置重连的时间间隔。那具体该如何使用呢?又该如何实现来实现重连呢?

首先需要纠正大家一个误区!!!:

        EventSource(SSE)本身并没有提供自动重连的机制,它所谓的自动重连特性是指浏览器自动处理与服务器的连接断开并尝试重新连接的过程。

EventSource retry 重连字段的介绍:

        当使用 EventSource 建立连接后,如果连接中断,浏览器会自动尝试重新连接服务器。这意味着浏览器会在连接中断后自动发起新的连接请求,以尝试重新建立与服务器的连接,以确保数据的实时传输。其中EventSource 对象提供了 retry 字段用于指定在连接中断后重新连接的时间间隔(以毫秒为单位)。当连接中断后,浏览器会根据 retry 字段的值来确定重新连接的时间间隔。

retry 字段的使用方法如下:

        下面的示例中,我们将 eventSource 对象的 retry 字段设置为 3000,表示在连接中断后,浏览器会每隔 5 秒尝试重新连接服务器一次。

let eventSource = new EventSource('your_event_source_url');
eventSource.retry = 5000; // 设置重新连接的时间间隔为3秒(3000毫秒)

        需要注意的是,retry 字段是可选的,如果不设置 retry 字段,浏览器会使用默认的重新连接时间间隔。另外,一些服务器端也可能会忽略 retry 字段,因此在实际使用中需要根据具体情况进行调整。总之,通过设置 retry 字段,开发者可以控制浏览器在连接中断后重新连接的时间间隔,以满足实际应用的需求。

        但是浏览器在连接中断后会自动尝试重新连接,这种重连机制并不是完全可靠的,有时候可能会出现连接失败的情况。这个时候我们需要手动干预重连。

浏览器自动重连注意事项:

  1. EventSource 对象的默认重新连接时间间隔是 3 秒。这意味着如果未显式设置 retry 字段,浏览器会在连接中断后每隔 3 秒尝试重新连接服务器一次。
  2.  在浏览器的开发者工具中的 Network 面板通常不会显示 EventSource 的重新连接过程,因为 EventSource 是基于长轮询机制的,浏览器会在后台自动处理重新连接过程,而不会在 Network 面板中显示每次重新连接的请求。
  3. 在浏览器中,EventSource 对象会自动处理网络连接的断开和重连,以确保与服务器的连接保持活动状态。当连接断开时,浏览器会自动尝试重新连接,这个过程是由浏览器内部处理的,不会触发 EventSource 对象的 onerror 或 onclose 事件。
  4. 如果想要观察 EventSource 的连接情况,可以在控制台中输出相关信息或者利用事件监听来捕获连接状态的变化。通过监听 EventSource 对象的事件(如 onopen、onerror 等),可以观察连接的建立和断开情况,以及根据需要执行相应的重连逻辑

手动重连实现:

       这里就是来专门解释注意事项的第三点(捕捉EventSource的相关信息从而进行重连),也就是我们编写相关代码去参与干预重连机制。在这里强调一点:进行手动重连之前,请务必保证之前的SSE连接已经断开!!!

实现方法:

  1. 使用错误处理机制:在 eventSource 连接中断后,可以通过监听 error 事件来捕获连接错误,并在错误处理函数中尝试重新连接。例如,可以在 error 事件处理函数中调用 eventSource 的 close 方法关闭连接,然后再调用 eventSource 的 open 方法重新建立连接。
    let eventSource = new EventSource('your_event_source_url');
    
    eventSource.addEventListener('error', function() {
      eventSource.close();
      eventSource = new EventSource('your_event_source_url');
    });
  2. 使用手动重连按钮:在 eventSource 连接中断后,可以显示一个按钮供用户手动触发重新连接操作。例如,可以在页面上添加一个按钮,并在按钮点击事件中调用 eventSource 的 close 方法关闭连接,然后再调用 eventSource 的 open 方法重新建立连接。这种方法可以让用户自主决定何时重新连接,增加了灵活性。
    let eventSource = new EventSource('your_event_source_url');
    let reconnectButton = document.getElementById('reconnectButton');
    
    reconnectButton.addEventListener('click', function() {
      eventSource.close();
      eventSource = new EventSource('your_event_source_url');
    });

示例: 

        EventSource 中断后进行重连,但是重连次数不超过 3 次,并且每次重连间隔为 6 秒。

let eventSource;
let reconnectCount = 0;
const maxReconnectAttempts = 3;
const reconnectInterval = 6000; // 6 秒

function connectEventSource() {
  eventSource = new EventSource('your_event_source_url');

  eventSource.onopen = function(event) {
    console.log('Connection opened');
    reconnectCount = 0; // 重置重连次数
  };

  eventSource.onerror = function(event) {
    console.error('Connection error:', event);
    if (reconnectCount < maxReconnectAttempts) {
      reconnectCount++;
      console.log(`Reconnecting attempt ${reconnectCount} in 6 seconds...`);
      setTimeout(() => {
        connectEventSource();
      }, reconnectInterval);
    } else {
      console.log('Exceeded maximum reconnection attempts.');
      eventSource.close(); // 关闭 EventSource 连接
    }
  };
}

connectEventSource();

但是在示例中其实有一个特别重要的点,我们是没有考虑到的:

        如果在代码中实现了自定义的重连逻辑(如上面的示例代码),浏览器的自动重连机制和自定义重连逻辑可能会冲突,相互干扰,导致重连次数超过预期。因此当在 onerror 事件处理程序中编写重连逻辑时,可能会导致浏览器和服务器之间的 EventSource 连接频繁断开和重连,从而在网络面板中出现大量的 SSE 连接。

解决办法:

  1. 使用 eventSource.readyState 属性来检查连接状态,避免重复重连:
    let eventSource = new EventSource('your_endpoint');
    
    eventSource.onopen = () => {
      console.log('Connection established');
    };
    
    eventSource.onerror = () => {
      if (eventSource.readyState === EventSource.CLOSED) {
        console.log('Connection closed, will not attempt to reconnect');
      }
    };
  2. 结合自己项目所使用的UI框架的生命周期来实现,这里将展示Angular框架的实现:
     reConnectHttp = false;
     preReConnectHttp:boolean;
     reConnectHttpCount = 1;
     httpUrl:string;// your sse url
     source:any;
    
     ngOnInit() {
        this.connectEventSource(this.httpUrl);
     }
      
     ngDoCheck() {
        if(this.reConnectHttp != this.preReConnectHttp) {
          this.preReConnectHttp = this.reConnectHttp;
        }else {
          return;
        }
        if(this.reConnectHttp && this.source readyState === EventSource.CLOSED) {) {
          if(this.reConnectHttpCount > 1 && this.reConnectHttpCount <=3) {
            this.reConnectHttpCount++;
            setTimeout(() => {
              this.connectEventSource(this.httpUrl);
            },3000);
          }else if(this.reConnectHttpCount === 1){
            this.reConnectHttpCount++;
            this.connectEventSource(this.httpUrl);
          }
        }
      }
    
      /**
       * connect event source.
       */
      connectEventSource(url:string) {
        this.messages = [];
        this.messagesDetails = _.cloneDeep([]);
        this.reConnectHttp = false;
        this.preReConnectHttp = false;
        let that = this;
        this.ctrl = new AbortController();
        let t1 = new Date().getTime();
        this.source = fetchEventSource(url, {
          method: 'GET',
          headers: {
           'Content-Type': 'application/json',
           'Accept':'text/event-stream'
          },
          // body: JSON.stringify({}),//{} body params
          signal: this.ctrl.signal,
          openWhenHidden: true, // This is important to avoid the connection being closed when the tab is not active
          async onopen(response) {
            let t2 = new Date().getTime();
            if (response.ok && response.headers.get('content-type') === 'text/event-stream') {
              console.log('eventSource Start:', t2, ', diff:', t2 - t1);
              that.reConnectHttpCount = 1;
            } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
              console.log('eventSource Request error!', t2, ', diff:', t2 - t1);
              that.reConnectHttp = true;
            } else {
              console.log('eventSource Other error!', t2, ', diff:', t2 - t1);
              that.reConnectHttp = true;
            }
          },
          async onmessage(event) {
            let t3 = new Date().getTime();
            console.log('--eventSource data--', event, t3, ', diff:', t3 - t1);
            event.data && that.messageRecieved(event.data);
          },
          onerror(error) {
            let t4 = new Date().getTime();
            console.error('eventSource Error:', error, t4, ', diff:', t4 - t1);
            that.ctrl.abort();
            that.reConnectHttp = true;
            throw error;
          },
          async onclose() {
            let t5 = new Date().getTime();
            console.log('eventSource Close connection', t5, ', diff:', t5 - t1);
            that.ctrl.abort();
            that.reConnectHttp = true;
            // if the server closes the connection unexpectedly, retry:
            return;
          }
        }).then((response) => {
            console.log('--eventSource response--', response);
        }).then((data) => console.log('--eventSource then data--', data)).catch((error) => console.error('eventSource Error:', error));
      }
  3. 在 onerror 事件处理程序中添加延迟重连,以避免过于频繁地尝试重连:
    let eventSource = new EventSource('your_endpoint');
    let reconnectTimeout = 5000; // 5 seconds
    
    eventSource.onerror = () => {
      setTimeout(() => {
        eventSource.close();
        eventSource = new EventSource('your_endpoint');
      }, reconnectTimeout);
    };
  4. 使用 eventSource.onclose 事件处理程序来处理连接关闭的情况,而不是仅依赖于 onerror
    let eventSource = new EventSource('your_endpoint');
    
    eventSource.onopen = () => {
      console.log('Connection established');
    };
    
    eventSource.onerror = () => {
      console.log('Connection error');
    };
    
    eventSource.onclose = () => {
      console.log('Connection closed');
      // Optionally, you can attempt to reconnect here
    };

By the Way: 

        readyState属性:检查 EventSource 对象的连接状态,只读属性,可以返回当前连接的状态,具体取值如下:

  • EventSource.CONNECTING (0): 表示连接正在建立中。
  • EventSource.OPEN(1): 表示连接已经建立并处于打开状态。
  • EventSource.CLOSED(2): 表示连接已经关闭。

        我们可以根据 readyState的值来执行相应的逻辑,以确保连接的稳定性和正确性。以下是一个示例代码:

function fetchEventSource(url) {
  let eventSource = new EventSource(url);

  // Check the readyState of the EventSource object
  if (eventSource.readyState === EventSource.CONNECTING) {
    console.log('Connection is in the process of being established');
  } else if (eventSource.readyState === EventSource.OPEN) {
    console.log('Connection is open');
  } else if (eventSource.readyState === EventSource.CLOSED) {
    console.log('Connection is closed');
  }

  // Handle other EventSource events as needed
  eventSource.onopen = () => {
    console.log('Connection established');
  };

  eventSource.onerror = () => {
    console.log('Connection error');
  };

  eventSource.onclose = () => {
    console.log('Connection closed');
  };

  return eventSource;
}

// Example usage of fetchEventSource method
let url = 'your_endpoint';
let eventSource = fetchEventSource(url);
  • 54
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EventSource是HTML5中的一个API,它允许浏览器与服务器建立一条单向通道,通过这条通道服务器可以向浏览器发送事件消息。这种技术常用于推送实时数据、通知等场景。相比于传统的轮询或长轮询方式,EventSource可以节省网络带宽和服务器资源。 使用EventSource时,浏览器通过JavaScript代码创建一个EventSource对象,指定服务器的URL地址,然后监听服务器发送的事件消息。服务器可以通过向浏览器发送特定格式的文本数据来触发事件消息。浏览器收到事件消息后,可以通过回调函数处理消息内容。 需要注意的是,EventSource只支持单向通信,即服务器只能向浏览器发送消息,而不能接收浏览器发送的消息。同时,EventSource也有一些限制,如不能发送二进制数据、不能跨域等。 以下是EventSource的一些常用属性和方法: 属性: - readyState:连接状态,包括CONNECTING(正在连接)、OPEN(已连接)、CLOSED(已关闭)三种状态。 - url:服务器地址。 - withCredentials:是否允许发送跨域凭证。 方法: - close():关闭EventSource连接。 - addEventListener():添加事件监听器。 示例代码: ``` var source = new EventSource('/server'); source.addEventListener('message', function(event) { console.log(event.data); }, false); ``` 在上面的代码中,创建了一个EventSource对象,指定服务器地址为/server,然后通过addEventListener方法添加了一个message事件监听器,当服务器发送消息时,会触发该事件监听器,并打印出消息内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值