进入本文博客正题之前,或者对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 字段,开发者可以控制浏览器在连接中断后重新连接的时间间隔,以满足实际应用的需求。
但是浏览器在连接中断后会自动尝试重新连接,这种重连机制并不是完全可靠的,有时候可能会出现连接失败的情况。这个时候我们需要手动干预重连。
浏览器自动重连注意事项:
手动重连实现:
这里就是来专门解释注意事项的第三点(捕捉EventSource的相关信息从而进行重连),也就是我们编写相关代码去参与干预重连机制。在这里强调一点:进行手动重连之前,请务必保证之前的SSE连接已经断开!!!
实现方法:
let eventSource = new EventSource('your_event_source_url');
eventSource.addEventListener('error', function() {
eventSource.close();
eventSource = new EventSource('your_event_source_url');
});
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 连接。
解决办法:
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');
}
};
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));
}
onerror
事件处理程序中添加延迟重连,以避免过于频繁地尝试重连: let eventSource = new EventSource('your_endpoint');
let reconnectTimeout = 5000; // 5 seconds
eventSource.onerror = () => {
setTimeout(() => {
eventSource.close();
eventSource = new EventSource('your_endpoint');
}, reconnectTimeout);
};
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 对象的连接状态,只读属性,可以返回当前连接的状态,具体取值如下:
我们可以根据 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);