I'm currently following this example and I'm tring to extend upon it with my own logic.
I can get the socket to connect, but I see that there is a problem in having the client reconnect continuously - Hence why I want to have a limit on the number of retries that the client can perform.
socket.pipe(
tap((data => console.log(data))),
retryWhen((errors) =>
errors.pipe(
take(this.retryCount),
delayWhen(val =>
timer(val * 1000)
)
)
)
).subscribe()
I looked up the documentation and figured that I could use the take() operator from rxjs. However, given that I set my retryCount to 5, the following scenario can be assumed:
Client connects to socket successfully
Client disconnects from the socket
Client reconnects after 4 retries
Client disconnects
Client retries 1 times, and stops retrying (take(5) has been reached)
Is there a way, in which I can "reset" the amount of times that take() will retry? i.e. so that every time the client disconnects from the socket, it will always have 5 retries?
The retryWhen logic from your example doesn't take into account all the possible intertwined flows of connect/disconnect/reconnect. Also the reset of the reconnection attempts doesn't happen.
Beside that, you're touching the inner Observables for open and close of the webSocket directly (which is not technically wrong), while you could put all the logic and handlers inside the subject's pipe and subscription code. I've forked your example from StackBlitz and adjusted it so that any connect / disconnect and reconnect events are handled properly.
As you can see in the screenshot below, it's trying to reconnect five time before it reaches the limit and quit.
The number of reconnect retries and the delay between each trial can be tweaked via this two constants:
const maxReconnectAttempts = 5; // -1 stands for 'forever'
const reconnectAttemptDelay = 1000; // in ms
As written in the comment, if you put const maxReconnectAttempts = -1 the reconnection trials will go on forever.
You can find below the whole code taken from index.js, or here in the forked StackBlitz repo for the full reference.
import { iif, of, throwError } from 'rxjs';
import { concatMap, delay, retryWhen, tap } from 'rxjs/operators';
import { webSocket } from 'rxjs/webSocket';
// get elements
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
const display = document.getElementById('app');
const msg = document.getElementById('msg');
// bind click actions
btn1.onclick = connect;
btn2.onclick = disconnect;
// create a webSocketSubject
const wsUrl = 'wss://www.gasnow.org/ws/gasprice';
const maxReconnectAttempts = 5; // -1 stands for 'forever'
const reconnectAttemptDelay = 1000; // in ms
let wsSubject$;
let wsSubscription;
let disconnected = false;
let reconnectRetryCount = 0;
// display live data on view
const bindDataToView = ({ data }) => {
console.log(data);
display.innerText = Number(data.rapid);
};
// connect/subscribe to websocket url
function connect() {
if (!this.wsSubject || this.wsSubject.closed) {
wsSubject$ = webSocket(wsUrl); // create a fresh instance
console.log(`Initializing WebSocket connection to ${wsUrl}...`);
wsSubscription = wsSubject$
.pipe(
retryWhen((errors) =>
errors.pipe(
concatMap((error) =>
iif(
() =>
maxReconnectAttempts !== -1 &&
reconnectRetryCount++ >= maxReconnectAttempts,
throwError('WebSocket reconnecting retry limit exceeded!'),
of(error).pipe(
tap(() => {
disconnected = true;
console.warn('Trying to reconnect to WebSocket server...');
}),
delay(reconnectAttemptDelay)
)
)
)
)
),
tap(() => {
if (disconnected) {
disconnected = false;
reconnectRetryCount = 0;
msg.innerText = 'Streaming ...';
console.log('Successfully reconnected to the WebSocket server.');
}
})
)
.subscribe(
(data) => {
msg.innerText = 'Streaming ...';
bindDataToView(data);
},
(err) => {
reconnectRetryCount = 0;
console.error(err);
},
() => {
reconnectRetryCount = 0;
msg.innerText = 'Connection closed';
console.warn('Connection to the WebSocket server was closed!');
}
);
}
}
// close websocket connection
function disconnect() {
if (wsSubject$) {
wsSubject$.complete(); // this will trigger closingObserver and closeObserver
wsSubject$.unsubscribe();
wsSubject$ = null;
disconnected = true;
reconnectRetryCount = 0;
if (wsSubscription) {
wsSubscription.unsubscribe();
wsSubscription = null;
}
console.log('Disconnected from the WebSocket server.');
}
}
Related
I'm running on a fastify server and when I send a request, I get a ton of data from mongodb and start a for loop to process each item. This can take ~ 30 minutes. Each item is "processed" by sending to ffmpeg, redis pub->sub, and then a socket to the client.
// Streams controller
exports.renderStreams = async function (req, reply) {
const streams = await Stream.find({}).sort({ createdAt: -1 })//.limit(5)
const renderedStreams = renderStreams(this, streams);
return { success: true, streams: streams.length };
}
// renderStreams
const renderStreams = (fastify, streams = []) => {
const { redis } = fastify;
const channel = "streams";
for (let i = 0; i < streams.length; i++) {
setTimeout(async () => {
const stream = streams[i];
await renderStream(redis, channel, stream);
}, i * 200)
}
}
I am wondering in this for loop, how can I either "pause" it or stop it completely (or both?) via another request, maybe when I call /api/streams/stop.
How would this be possible?
You can use socket.io to do communications with your script while it is still running, so you would create a function to stop the loop and when you get the notification from socket.io you would run it. Documentation link: https://socket.io/docs/v4/
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = function () {
ws.send(JSON.stringify({
.... some message the I must send when I connect ....
}));
};
ws.onmessage = function (e) {
console.log('Got a message')
console.log(e.data);
};
ws.onclose = function(e) {
console.log('socket closed try again');
}
ws.onerror = function(err) {
console.error(err)
};
When I first connect to the socket, I must first send a message to the server to authenticate myself and subscribe to channels.
The problem I have is that sometimes the socket server is unreliable and that triggers the onerror and onclose events of the 'ws' object.
Question: What is a good design pattern that would allow me, whenever the socket closes or encounters an error, wait for 10 seconds and then reconnect to the socket server (and resend the initial message to the server)
Here is what I ended up with. It works for my purposes.
function connect() {
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = function() {
// subscribe to some channels
ws.send(JSON.stringify({
//.... some message the I must send when I connect ....
}));
};
ws.onmessage = function(e) {
console.log('Message:', e.data);
};
ws.onclose = function(e) {
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
setTimeout(function() {
connect();
}, 1000);
};
ws.onerror = function(err) {
console.error('Socket encountered error: ', err.message, 'Closing socket');
ws.close();
};
}
connect();
This worked for me with setInterval, because client connection can be lost.
ngOnInit(): void {
if (window.location.protocol.includes('https')) {
this.protocol = 'wss';
}
this.listenChanges();
}
listenChanges(): void {
this.socket = new WebSocket(`${this.protocol}://${window.location.host}/v1.0/your/url`);
this.socket.onmessage = (event): void => {
// your subscription stuff
this.store.dispatch(someAction);
};
this.socket.onerror = (): void => {
this.socket.close();
};
this.socket.onopen = (): void => {
clearInterval(this.timerId);
this.socket.onclose = (): void => {
this.timerId = setInterval(() => {
this.listenChanges();
}, 10000);
};
};
}
Don't forget to call clearInterval when the socket has been opened.
This isn't explicitly a react question but here is a react style answer:
TLDR: You can use setInterval to periodically check the websocket connection status and try to re-connect if the connection is closed. https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.connect = this.connect.bind(this);
}
componentDidMount() {
this.interval = setInterval(this.connect, 1000);
}
componentWillUnmount() {
if (this.ws) this.ws.close();
if (this.interval) clearInterval(this.interval);
}
connect() {
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
if (this.ws === undefined || (this.ws && this.ws.readyState === 3)) {
this.ws = new WebSocket(`ws://localhost:8080`);
this.ws.onmessage = (e) => {
console.log(JSON.parse(e.data));
};
}
}
render() {
return <div>Hey!</div>;
}
}
I found that this package https://github.com/pladaria/reconnecting-websocket can solve the reconnection issues for Websocket connections. And it has the list of configurable options, one of them is reconnectionDelayGrowFactor which determines how fast the reconnection delay grows.
using async-await if socket closed or any error occurred on the server the client will try to connect automatically every 5 sec forever
have a look to my answer
UPDATED answer:
At last, (if you are not using java) I found you'd better implement your own "ping/pong" strategy. (if you are using java, please take a look at ping/pong "action type", I don't remember very clear... )
client sent "ping" to server every 5 seconds.
server should echo a "pong" to the client once it receive "ping".
client should reconnect server if doesn't receive "pong" in 5 seconds.
Don't rely on any third party libs.
WARNING: DO NOT use these tools: (reason: they are not reliable and not stable and works in a very limited way. )
check if the network is available: https://github.com/hubspot/offline
to re-connect: https://github.com/joewalnes/reconnecting-websocket
You can use a small library if you want - ReconnectingWebSocket
Add reconnecting-websocket.js in your script tag and
It is API compatible, so when you have:
var ws = new WebSocket('ws://....');
you can replace with:
var ws = new ReconnectingWebSocket('ws://....');
Try this:
const observable = Observable.create(
(obs: Observer<MessageEvent>) => {
this.ws.onmessage = obs.next.bind(obs);
this.ws.onerror = obs.error.bind(obs);
// this.ws.onclose = obs.complete.bind(obs);
this.ws.onclose = function () {
window.location.reload()
}
return this.ws.close.bind(this.ws);
});
const observer = {
next: (data: Object) => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
};
and component
getDatas() {
let url = environment.apiwebsocket
this.webSocketService.connect(url)
.subscribe(evt => {
let jsonObj = JSON.parse(evt.data)
});}
I used to have this somewhere in project:
let rc = new WebSocket(
'ws://'
+ window.location.host
+ `/ws/chat/${window.seen.pk}/`
)
now I switched to:
// ws create the websocket and returns it
function autoReconnect(ws_create){
let ws = ws_create();
function startReconnecting(){
let interval = setInterval(()=>{
console.log('trying')
ws = ws_create();
ws.onopen = () => {
console.log('stop');
ws.onclose = startReconnecting;
clearInterval(interval);
}
}, 3000);
}
ws.onclose = startReconnecting;
}
let rc;
autoReconnect(()=>{
rc = new WebSocket(
'ws://'
+ window.location.host
+ `/ws/chat/${window.seen.pk}/`
)
return rc;
});
test it by running and stop local host, it works fine. (btw I found it weird this question has been posted for a long time, but there is not a short and elegant solution)
the benefit of this method, is that it allows you to pass in an arrow function, so that you can assign variable to any outer scope.
Here's a simple version I use in my projects. It includes an incrementing wait timer for reconnects.
//wsURL - the string URL of the websocket
//waitTimer - the incrementing clock to use if no connection made
//waitSeed - used to reset the waitTimer back to default on a successful connection
//multiplier - how quickly you want the timer to grow on each unsuccessful connection attempt
const openSocket = (wsURL, waitTimer, waitSeed, multiplier) =>{
let ws = new WebSocket(wsURL);
console.log(`trying to connect to: ${ws.url}`);
ws.onopen = () => {
console.log(`connection open to: ${ws.url}`);
waitTimer = waitSeed; //reset the waitTimer if the connection is made
ws.onclose = () => {
console.log(`connection closed to: ${ws.url}`);
openSocket(ws.url, waitTimer, waitSeed, multiplier);
};
ws.onmessage = (message) => {
//do something with messge...
};
};
ws.onerror = () => {
//increaese the wait timer if not connected, but stop at a max of 2n-1 the check time
if(waitTimer < 60000) waitTimer = waitTimer * multiplier;
console.log(`error opening connection ${ws.url}, next attemp in : ${waitTimer/1000} seconds`);
setTimeout(()=>{openSocket(ws.url, waitTimer, waitSeed, multiplier)}, waitTimer);
}
}
openSocket(`ws://localhost:3000`, 1000, 1000, 2)
Alternatively you can explore socket.io. It offers this feature
on the client side you indicate reconnection: true
const io = require("socket.io-client");
const socket = io('ws://'+WS_REMOTE_ADDRESS,{
reconnection: true,
});
So, for a course i'm taking, we're coding a UDP pinger in Javascript, using Node.js and Dgram. We've been given the following assignment:
Create the client code for an application. Your client should send 10 ping messages to the target UDP server. For each message, your client should calculate the round trip time from when the package is sent to when the response is received. Should a package be dropped along the way, the client is to handle this as well. This should be done by having the client wait 1 second for a response after sending each package. If no reply is received, the client should log accordingly (package lost, no response, timeout, etc.) and send a new package to try again. However, the total amount of packages sent should still only be 10. The client should also calculate a percentage of packages lost/no response received, and log this before connection is closed.
THis if course seems rather straight forward, and I thought so. I've been coding it for a while, and I'm almost finished, but I'm having issues with the aspect of making the client send a package, await response, and then act accordingly.
So far, what my code does is basically to send a ping, and when a pong is received, it sends another ping. What I can't figure out is how to make it log that a response wasn't received before sending the next package. In other words, I know how to make it react to a received response, I just don't know how to make it respond if no response is given within a set timeframe. I've tried playing around with if-statements and loops, as well as async functions, but I haven't made it work yet, so now I'm asking for help.
Code is here:
const dgram = require("dgram");
const ms = require("ms");
var client = dgram.createSocket("udp4");
const PORT = 8000;
const HOST = "localhost";
let today = "";
let t0 = "";
let t1 = "";
let RTT = "";
let sentPackages = "";
let receivedPackages = "";
const messageOutbound = Buffer.from("You Up?");
sendPackage();
const x = setInterval(sendPackage, 1000);
client.on("message", (message, remote) => {
receivedPackages++
today = new Date();
t1 = today.getTime();
console.log(
`Message from: ${remote.address}:${remote.port} saying: ${message}`
);
RTT = ms(t1 - t0, { long: true });
console.log(RTT);
const x = setInterval(sendPackage, 1000);
});
client.on('error', (err) => {
console.log(`server error:\n${err.stack}`);
server.close();
});
async function sendPackage() {
if (sentPackages < 10) {
client.send(messageOutbound, 0, messageOutbound.length, PORT, HOST, () => {
sentPackages++
let today = new Date();
t0 = today.getTime();
console.log(
`message has been sent to ${HOST}:${PORT}. Message sent at: ${t0}`
);
});
} else {
calculateLoss();
client.close();
}
};
function calculateLoss() {
let amountLost = sentPackages - receivedPackages;
let percentageLoss = amountLost / sentPackages * 100
console.log(amountLost);
console.log(percentageLoss +"% of packages lost");
};
I would use async / await to simply wait 1000ms / 1s between messages, then keep track of all messages in an array.
We identify messages with a uuid, so we can ensure that messages we receive can be matched to those we send.
We can then log all the required statistics afterwards:
const dgram = require("dgram");
const uuid = require('uuid');
const PORT = 8000;
const HOST = "localhost";
const client = dgram.createSocket("udp4");
// Array that keeps track of the messages we send
let messages = [];
// When we get a message, decode it and update our message list accordingly...
client.on("message", (messageBuffer, remote) => {
let receivedMessage = bufferToMessage(messageBuffer);
// Find the message we sent and set the response time accordingly.
let message = messages.find(message => message.uuid === (receivedMessage ||{}).uuid);
if (message) {
message.responseTimestamp = new Date().getTime();
}
});
client.on('error', (err) => {
console.log(`server error:\n${err.stack}`);
server.close();
});
function createMessage() {
return { uuid: uuid.v4() };
}
function messageToBuffer(message) {
return Buffer.from(JSON.stringify(message), "utf-8");
}
function bufferToMessage(buffer) {
try {
return JSON.parse(buffer.toString("utf-8"));
} catch (error) {
return null;
}
}
// Wait for timeout milliseconds
function wait(timeout) {
return new Promise(resolve => setTimeout(resolve, timeout));
}
function sendMessage(message, port, host) {
// Save the messages to our list...
messages.push(message);
console.log(`Sending message #${messages.length}...`);
// Set the time we send out message...
message.sentTimestamp = new Date().getTime();
let messageBuffer = messageToBuffer(message);
return new Promise((resolve, reject) => {
client.send(messageBuffer, 0, messageBuffer.length, port, host, (error, bytes) => {
if (error) {
reject(error);
} else {
resolve(bytes);
}
})
});
}
async function sendMessages(messageCount, port, host, timeout) {
for(let messageIndex = 0; messageIndex < messageCount; messageIndex++) {
let message = createMessage();
await sendMessage(message, port, host);
await wait(timeout);
if (message.responseTimestamp) {
console.log(`Response received after ${message.responseTimestamp - message.sentTimestamp} ms...`);
} else {
console.log(`No response received after ${timeout} ms...`);
}
}
logStatistics(messages);
}
function logStatistics(messages) {
let messagesSent = messages.length;
let messagesReceived = messages.filter(m => m.responseTimestamp).length;
let messagesLost = messagesSent - messagesReceived;
console.log(`Total messages sent: ${messagesSent}`);
console.log(`Total messages received: ${messagesReceived}`);
console.log(`Total messages lost: ${messagesLost} / ${(100*messagesLost / (messages.length || 1) ).toFixed(2)}%`);
if (messagesReceived > 0) {
console.log(`Average response interval:`, messages.filter(m => m.responseTimestamp).reduce((averageTime, message) => {
averageTime += (message.responseTimestamp - message.sentTimestamp) / messagesReceived;
return averageTime;
}, 0) + " ms");
}
}
sendMessages(10, PORT, HOST, 1000);
I have 9 websocket connections from different sites working to update the DOM with data. Currently I am connecting to all and listening to all websockets and updating the data with a function call.
The issue I'm facing is that there are many websocket connections and there are memory and CPU usage issues. How can I use either service workers and web workers to optimize so many websocket connections?
async function appendGatePublicTickersData(e) {
if (e.event == "update" && e.result[0].contract == "BTC_USD") {
if ('total_size' in e.result[0]) {
$(".gate-btc-open-interest").html(commaNumber(e.result[0].total_size))
if ('last' in e.result[0]) {
$(".gate-btc-open-value").html(commaNumber(customFixedRounding((e.result[0].total_size / e.result[0].last), 4)))
}
}
if ('volume_24h_usd' in e.result[0]) {
$(".gate-btc-24-volume").html(commaNumber(e.result[0].volume_24h_usd))
}
if ('volume_24h_btc' in e.result[0]) {
$(".gate-btc-24-turnover").html(commaNumber(e.result[0].volume_24h_btc))
}
if ('funding_rate' in e.result[0]) {
var fundingRateBtcGate = customFixedRounding(e.result[0].funding_rate * 100, 4)
$(".public-gate-btc-funding").html(fundingRateBtcGate)
}
if ('funding_rate_indicative' in e.result[0]) {
var predictedRateBtcGate = customFixedRounding(e.result[0].funding_rate_indicative * 100, 4)
$(".public-gate-btc-predicted").html(predictedRateBtcGate)
}
}
}
var pubGateWs = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/btc");
pubGateWs.addEventListener("open", function() {
pubGateWs.send(JSON.stringify({
"time": 123456,
"channel": "futures.tickers",
"event": "subscribe",
"payload": ["BTC_USD", "ETH_USD"]
}))
});
pubGateWs.addEventListener("message", function(e) {
e = JSON.parse(e.data)
appendGatePublicTickersData(e)
});
pubGateWs.addEventListener("close", function() {});
Since you are using Web Sockets it would be a good idea to use a SharedWorker to create a new thread for your Web Sockets. The difference between a normal WebWorker and a SharedWorker is that the web worker will create a new session in each tab or browser when loading the page, whereas the shared worker will use the same session in each tab. So all of your tabs or windows will have the same worker and same Web Socket connection to work with.
If the data is updated very frequently (more than 60 times per second) and the DOM has to be updated every time that happens, then use the requestAnimationFrame method to throttle the amount that the DOM is being updated. It will wait for the next repaint cycle before updating the DOM with new content, which is about 60 times per second, or 60FPS.
An implementation of this would like the example below:
Main thread.
// Create shared worker.
const webSocketWorker = new SharedWorker('web-sockets-worker.js');
/**
* Sends a message to the worker and passes that to the Web Socket.
* #param {any} message
*/
const sendMessageToSocket = message => {
webSocketWorker.port.postMessage({
action: 'send',
value: message,
});
};
// Event to listen for incoming data from the worker and update the DOM.
webSocketWorker.port.addEventListener('message', ({ data }) => {
requestAnimationFrame(() => {
appendGatePublicTickersData(data);
});
});
// Initialize the port connection.
webSocketWorker.port.start();
// Remove the current worker port from the connected ports list.
// This way your connectedPorts list stays true to the actual connected ports,
// as they array won't get automatically updated when a port is disconnected.
window.addEventListener('beforeunload', () => {
webSocketWorker.port.postMessage({
action: 'unload',
value: null,
});
webSocketWorker.port.close();
});
Shared Worker.
/**
* Array to store all the connected ports in.
*/
const connectedPorts = [];
// Create socket instance.
const socket = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/btc");
// Send initial package on open.
socket.addEventListener('open', () => {
const data = JSON.stringify({
"time": 123456,
"channel": "futures.tickers",
"event": "subscribe",
"payload": ["BTC_USD", "ETH_USD"]
});
socket.send(data);
});
// Send data from socket to all open tabs.
socket.addEventListener('message', ({ data }) => {
const payload = JSON.parse(data);
connectedPorts.forEach(port => port.postMessage(payload));
});
/**
* When a new thread is connected to the shared worker,
* start listening for messages from the new thread.
*/
self.addEventListener('connect', ({ ports }) => {
const port = ports[0];
// Add this new port to the list of connected ports.
connectedPorts.push(port);
/**
* Receive data from main thread and determine which
* actions it should take based on the received data.
*/
port.addEventListener('message', ({ data }) => {
const { action, value } = data;
// Send message to socket.
if (action === 'send') {
socket.send(JSON.stringify(value));
// Remove port from connected ports list.
} else if (action === 'unload') {
const index = connectedPorts.indexOf(port);
connectedPorts.splice(index, 1);
}
});
// Start the port broadcasting.
port.start();
});
Sidenote: your appendGatePublicTickersData does not use the await keyword, so it does not have to be an async function.
Now supported since Safari 16.
Browser support for Shared Web Workers
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = function () {
ws.send(JSON.stringify({
.... some message the I must send when I connect ....
}));
};
ws.onmessage = function (e) {
console.log('Got a message')
console.log(e.data);
};
ws.onclose = function(e) {
console.log('socket closed try again');
}
ws.onerror = function(err) {
console.error(err)
};
When I first connect to the socket, I must first send a message to the server to authenticate myself and subscribe to channels.
The problem I have is that sometimes the socket server is unreliable and that triggers the onerror and onclose events of the 'ws' object.
Question: What is a good design pattern that would allow me, whenever the socket closes or encounters an error, wait for 10 seconds and then reconnect to the socket server (and resend the initial message to the server)
Here is what I ended up with. It works for my purposes.
function connect() {
var ws = new WebSocket('ws://localhost:8080');
ws.onopen = function() {
// subscribe to some channels
ws.send(JSON.stringify({
//.... some message the I must send when I connect ....
}));
};
ws.onmessage = function(e) {
console.log('Message:', e.data);
};
ws.onclose = function(e) {
console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason);
setTimeout(function() {
connect();
}, 1000);
};
ws.onerror = function(err) {
console.error('Socket encountered error: ', err.message, 'Closing socket');
ws.close();
};
}
connect();
This worked for me with setInterval, because client connection can be lost.
ngOnInit(): void {
if (window.location.protocol.includes('https')) {
this.protocol = 'wss';
}
this.listenChanges();
}
listenChanges(): void {
this.socket = new WebSocket(`${this.protocol}://${window.location.host}/v1.0/your/url`);
this.socket.onmessage = (event): void => {
// your subscription stuff
this.store.dispatch(someAction);
};
this.socket.onerror = (): void => {
this.socket.close();
};
this.socket.onopen = (): void => {
clearInterval(this.timerId);
this.socket.onclose = (): void => {
this.timerId = setInterval(() => {
this.listenChanges();
}, 10000);
};
};
}
Don't forget to call clearInterval when the socket has been opened.
This isn't explicitly a react question but here is a react style answer:
TLDR: You can use setInterval to periodically check the websocket connection status and try to re-connect if the connection is closed. https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.connect = this.connect.bind(this);
}
componentDidMount() {
this.interval = setInterval(this.connect, 1000);
}
componentWillUnmount() {
if (this.ws) this.ws.close();
if (this.interval) clearInterval(this.interval);
}
connect() {
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
if (this.ws === undefined || (this.ws && this.ws.readyState === 3)) {
this.ws = new WebSocket(`ws://localhost:8080`);
this.ws.onmessage = (e) => {
console.log(JSON.parse(e.data));
};
}
}
render() {
return <div>Hey!</div>;
}
}
I found that this package https://github.com/pladaria/reconnecting-websocket can solve the reconnection issues for Websocket connections. And it has the list of configurable options, one of them is reconnectionDelayGrowFactor which determines how fast the reconnection delay grows.
using async-await if socket closed or any error occurred on the server the client will try to connect automatically every 5 sec forever
have a look to my answer
UPDATED answer:
At last, (if you are not using java) I found you'd better implement your own "ping/pong" strategy. (if you are using java, please take a look at ping/pong "action type", I don't remember very clear... )
client sent "ping" to server every 5 seconds.
server should echo a "pong" to the client once it receive "ping".
client should reconnect server if doesn't receive "pong" in 5 seconds.
Don't rely on any third party libs.
WARNING: DO NOT use these tools: (reason: they are not reliable and not stable and works in a very limited way. )
check if the network is available: https://github.com/hubspot/offline
to re-connect: https://github.com/joewalnes/reconnecting-websocket
You can use a small library if you want - ReconnectingWebSocket
Add reconnecting-websocket.js in your script tag and
It is API compatible, so when you have:
var ws = new WebSocket('ws://....');
you can replace with:
var ws = new ReconnectingWebSocket('ws://....');
Try this:
const observable = Observable.create(
(obs: Observer<MessageEvent>) => {
this.ws.onmessage = obs.next.bind(obs);
this.ws.onerror = obs.error.bind(obs);
// this.ws.onclose = obs.complete.bind(obs);
this.ws.onclose = function () {
window.location.reload()
}
return this.ws.close.bind(this.ws);
});
const observer = {
next: (data: Object) => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
};
and component
getDatas() {
let url = environment.apiwebsocket
this.webSocketService.connect(url)
.subscribe(evt => {
let jsonObj = JSON.parse(evt.data)
});}
I used to have this somewhere in project:
let rc = new WebSocket(
'ws://'
+ window.location.host
+ `/ws/chat/${window.seen.pk}/`
)
now I switched to:
// ws create the websocket and returns it
function autoReconnect(ws_create){
let ws = ws_create();
function startReconnecting(){
let interval = setInterval(()=>{
console.log('trying')
ws = ws_create();
ws.onopen = () => {
console.log('stop');
ws.onclose = startReconnecting;
clearInterval(interval);
}
}, 3000);
}
ws.onclose = startReconnecting;
}
let rc;
autoReconnect(()=>{
rc = new WebSocket(
'ws://'
+ window.location.host
+ `/ws/chat/${window.seen.pk}/`
)
return rc;
});
test it by running and stop local host, it works fine. (btw I found it weird this question has been posted for a long time, but there is not a short and elegant solution)
the benefit of this method, is that it allows you to pass in an arrow function, so that you can assign variable to any outer scope.
Here's a simple version I use in my projects. It includes an incrementing wait timer for reconnects.
//wsURL - the string URL of the websocket
//waitTimer - the incrementing clock to use if no connection made
//waitSeed - used to reset the waitTimer back to default on a successful connection
//multiplier - how quickly you want the timer to grow on each unsuccessful connection attempt
const openSocket = (wsURL, waitTimer, waitSeed, multiplier) =>{
let ws = new WebSocket(wsURL);
console.log(`trying to connect to: ${ws.url}`);
ws.onopen = () => {
console.log(`connection open to: ${ws.url}`);
waitTimer = waitSeed; //reset the waitTimer if the connection is made
ws.onclose = () => {
console.log(`connection closed to: ${ws.url}`);
openSocket(ws.url, waitTimer, waitSeed, multiplier);
};
ws.onmessage = (message) => {
//do something with messge...
};
};
ws.onerror = () => {
//increaese the wait timer if not connected, but stop at a max of 2n-1 the check time
if(waitTimer < 60000) waitTimer = waitTimer * multiplier;
console.log(`error opening connection ${ws.url}, next attemp in : ${waitTimer/1000} seconds`);
setTimeout(()=>{openSocket(ws.url, waitTimer, waitSeed, multiplier)}, waitTimer);
}
}
openSocket(`ws://localhost:3000`, 1000, 1000, 2)
Alternatively you can explore socket.io. It offers this feature
on the client side you indicate reconnection: true
const io = require("socket.io-client");
const socket = io('ws://'+WS_REMOTE_ADDRESS,{
reconnection: true,
});