Socket Server Keeps Emitting More Values than Expected - node.js

I am using SocketIO in Node and React to display data in real-time from my backend server. I have a successful connection and I am getting a data stream. But I cannot understand why my server-side web socket is emitting more events than expected.
On the server side, the emission looks as follows:
io.on('connection', function(socket) {
console.log('Emitting')
setInterval(function() {
socket.emit('Echo', global.value);
}, 5000);
});
On the client side I have this:
const socket = io.connect('http://localhost:80/', {transports: ['websocket', 'polling', 'flashsocket']});
socket.on('connect', () => {
console.log('Connected to the websocket server');
});
socket.on('disconnect', () => {
console.log('Diconnected from the websocket server');
});
function App() {
// const [data, setData] = React.useState(null);
const [dataStream, setDataStream] = React.useState([
{ name: 0, x: 0, y: 0 },
{ name: 1, x: 1, y: 1 },
]);
function appendData(dataPoint) {
var prev = dataStream[dataStream.length - 1]
if (dataStream.length > 10) {
dataStream.reverse().pop()
dataStream.reverse()
}
setDataStream(oldArray => [...oldArray,
{ name: prev['name'] + 1, x: prev['x'] + 1, y: dataPoint} ]);
}
socket.once('Echo', (message) => {
console.log("echo")
appendData(message)
});
I am using socket.once on the client side. According to my understanding of how this should work, I should only get a single update every 5 seconds given that I set the interval on my server side to be 5000 ms.
Looking at the console log in my browser I see this
It appears the socket.emit on the server side is streaming on faster intervals than expected. Any help would be appreciated
I tried switching from socket.on to socket.once but that did not work either.

Related

Socket.IO is creating 2 socket IDS every time a new user joins a room instead of one

I am creating a collaborative react app, in that every time a new user is joining the room the socket io is generating 2 id's for every user, I have followed the documentation code, in the same way, I am not sure why is this happening, below is the snippet of the server-side code (server.js).
const cors = require('cors');
const axios = require('axios');
const {Server} = require('socket.io');
const http = require('http');
const ACTIONS = require('../src/Actions');
const app = express(); // Create an instance of express
const server = http.createServer(app) // Create an instance of http server
const io = new Server(server); // Create an instance of socket.io server
// Storing a client list
const clients = new Map();
// Switching on the server socket to listen for connections
io.on('connection', (socket) => {
const clientSocketId = socket.id;
console.log(clientSocketId+' connected');
socket.on(ACTIONS.JOIN,({roomId,username})=>{
console.log(roomId,username)
clients.set(socket.id,{
roomId,
username,
socketId: socket.id,
})
socket.join(roomId);
const clientlist = Array.from(clients.values())
clientlist.forEach(client=>{
io.to(client.socketId).emit(ACTIONS.JOINED,{
clientlist,
username,
socketId: socket.id,
})
})
})
// The server is listening to two events Code Change and Code Sync
// Code Change is emitted when the user changes the code
// Code Sync is called when the user joins the room to sync the previously typed code
socket.on(ACTIONS.CODE_CHANGE, ({ roomId, code }) => {
socket.in(roomId).emit(ACTIONS.CODE_CHANGE, { code });
});
socket.on(ACTIONS.SYNC_CODE, ({ socketId, code }) => {
io.to(socketId).emit(ACTIONS.CODE_CHANGE, { code });
});
// Disconnecting the current socket
socket.on('disconnecting',()=>{
console.log(clientSocketId+' disconnected')
// Getting the list of all the present rooms
const rooms = Object.keys(socket.rooms);
rooms.forEach(roomId=>{
socket.in(roomId).emit(ACTIONS.DISCONNECTED,{
socketId: socket.id,
username: clients.get(socket.id).username,
})
})
clients.delete(socket.id);
socket.leave();
})
})
const PORT = process.env.SERVER_PORT || 5000;
server.listen(PORT,()=>{console.log('Listening on '+PORT)});
And below is how I have initialized the socket on the client-side
export const initSocket = async () => {
const options = {
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 'Infinity',
forceNew: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 10000,
autoConnect: true,
secure: true,
}
const socket = io(process.env.REACT_APP_SERVER_URL,options)
return socket
}
And in my Dashboard.js I have called the init function in UseEffect
React.useEffect(()=>{
// As the user joins the room we initialize the client socket which connects to the server
const init = async () => {
socketRef.current = await initSocket();
// Handling connection errors
socketRef.current.on('connect_error',(err)=>handleError(err))
socketRef.current.on('connect_failed',(err)=>handleError(err))
const handleError = (err)=>{
console.log(err)
toast({
title: 'Error connecting to the server',
status: 'error',
duration: 9000,
isClosable: true,
})
reactNavigater('/')
}
socketRef.current.emit(ACTIONS.JOIN,{
roomId: roomId,
username: location.state?.username,
});
// Listening for joined event when a even user joins
socketRef.current.on(ACTIONS.JOINED,({clientlist,username,socketId})=>{
if(username !== location.state?.username){
toast({
title: `${username} has joined the room`,
status: 'success',
duration: 9000,
isClosable: true,
})
}
setClientlist(clientlist)
socketRef.current.emit(ACTIONS.SYNC_CODE, {
socketId: socketRef.current.id,
code: codeRef.current,
});
})
// Listening for disconnected event when a even user disconnects
socketRef.current.on(ACTIONS.DISCONNECTED,({socketId,username})=>{
toast({
title: `${username} has disconnected`,
status: 'warning',
duration: 9000,
isClosable: true,
})
// Filter the clientlist to remove the disconnected client
setClientlist(Clientlist.filter(client=>client.socketId !== socketId))
}
)
}
init()
// Here we have multiple listeners, so we have to remove them when the component unmounts
return ()=>{
if(socketRef.current){
socketRef.current.disconnect()
socketRef.current.off(ACTIONS.JOINED)
socketRef.current.off(ACTIONS.DISCONNECTED)
}
}
},[])
Any help would be appreciated
If you have strict mode on, what's by default, then useEffect is called twice (from React 18). And your connection is created twice. And as every connection generates new Id, you get two id's.
https://stackoverflow.com/a/72238236/8522881
My React Component is rendering twice because of Strict Mode
You can just wrap your socket.io instance with a useRef so it will keep the same value between re-renders, like:
import React from 'react'
function MyComponent() {
const socket = React.useRef(null)
React.useEffect(() => {
if(!socket.current) {
socket.current = io('ws://my-url/');
}
}, [])
// now you can use socket.current and ensure you aways will have only one instance
return (
<p>hello</p>
)
}

Unexpected behavior in sockets in nodejs (double connections,

PROBLEM:
So I have a reactjs application that I created with npx create-react-app.
I also have a server running on my machine on port 8174(no significance just random). On this server I have some socket.io action going on.
io.on('connection', (socket) => {
console.log('client connected')
}
Something weird which isn't really the problem, but may be related is that when I connect to the server it always runs twice. It will give me the "client connected" output twice in the server console.
The real problem is that I cannot figure out how to get this data into an array on the actual react application.
Here is the code in question:
import React, {useState, useEffect} from 'react'
import socketIOClient from 'socket.io-client'
import TweetCard from './TweetCard'
export default function TweetList() {
const [tweetItems, setTweetItems] = useState([])
const [socket] = useState(() => socketIOClient('http://localhost:8174/', {reconnection: true, forceNew: false}))
var items = []
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
const streamTweets = async () => {
socket.on('tweets', async data => {
await sleep(1000)
items = [...tweetItems, data]
items = items.reverse()
// items = items.filter(d => {return d.user != data.user})
var count = 15
items = items.filter(function(d) {
if(count != 0){
count--
return true
}
return false
})
console.log(items)
setTweetItems(items)
})
}
useEffect(() => {
socket.once('connect', () => {
console.log("Socket has successfully connected to server")
})
streamTweets()
return () => {
socket.on('disconnect', () => {
socket.off("tweets");
socket.removeAllListeners("tweets")
console.log("Socket Disconnected")
})
}
}, [])
return (
<div style={{ height: "300rem", scrollY: "auto", overflowY: "auto"}} >
{tweetItems.map(tweet => {
return <TweetCard tweet={tweet}/>
})}
</div>
)
}
THINGS I'VE TRIED:
On the server level I did a io.once instead of io.on. This fixed the issue of the client connecting twice or a crazy number of times like 5 in one second. It predictively only connected once. The problem with this however is if I refresh the page on the app it disconnects forever and I couldn't figure out how to make it reconnect even though it was saying it was reconnecting. I will post the server code on how it is connecting below, but basically it would output ('resuming stream') when it was infact not resuming the stream.
At first I just messed with the state directly (tweetItems). I basically make an array called newArr = [...tweetItems, data]. Then I set the state below it like setTweetItems(newArr). This however just updated index 0 every single time it set the state. So it would always be an array of one single item. It would update the next tweet though.
Tried using the bearer token instead of using require('twitter') package and tried implementing it in the 'twitter docs way of streaming' I can insert that link if needed. The code is outdated and incorrect though as I came to find out. I literally just forked the entire project at one point and put in my token. It did not work. They also use body parser still so its a strong sign that it is outdated
Tried making a regular array without the state called items =[] this worked for a little bit , and I am not sure what I changed, but it eventually started to copy like 2 or 3 of the same item. like index 0 - 2 would all be the same twee 3 - 4 would all be the same tweet and so on.
Also when I performed a "reverse()" on this items array it would give me a fatal error telling me that items = [...items, data] can't be set. This was odd because the code to reverse items was below this but the error was saying that items can't be set I am assuming it was doing something then after it "reversed" it went to set the items again and was null. I used items = items.reverse() this is what caused the error
Tried making the streaming of the tweets async in the react(did nothing)
Tried slowing down each setState by 1 second(did nothing)
I have tried many more things, but hopefully this will give you an idea of the issue that I am having. Any help or tutorials on sockets would be awesome.
Here is the code that connects to the twitter API:
const Twitter = require('twitter')
module.exports = (app, io) => {
var client = new Twitter({
consumer_key: process.env.CONSUMER_KEY,
consumer_secret: process.env.CONSUMER_SECRET,
access_token_key: process.env.ACCESS_TOKEN_KEY,
access_token_secret: process.env.ACCESS_TOKEN_SECRET
});
let timeout = 0
let socketConnection;
let twitterStream;
app.locals.searchTerm = 'giveaway'
app.locals.showRetweets = false;
//twitter stream
const streamtweets = () => {
console.log("Resuming stream for: " + app.locals.searchTerm)
client.stream('statuses/filter', {track: app.locals.searchTerm, tweet_mode: 'extended', language: 'en'}, (streamData) => {
streamData.on('data', (data) => {
sendMessage(data)
});
streamData.on('error', (error) => {
console.log(error)
})
twitterStream = streamData
})
}
const sleep = async (delay) => {
return new Promise((resolve) => setTimeout(() => resolve(true), delay));
};
//twitter stream
io.on('connection', socket => {
console.log(socket.id)
socketConnection = socket;
streamtweets();
socket.on("connection", () => console.log("Client has connected to the server"))
socket.on("disconnect", () => {
console.log("Client has disconnected to the server")
// twitterStream.destroy()
// socket.off()
// reconnect(twitterStream, socket)
})
})
const reconnect = async (stream, socket) => {
timeout++;
stream.destroy()
await sleep(2 ** timeout * 1000);
// streamTweets(socket, token);
streamtweets()
};
/**
* Sets search term for twitter stream.
*/
app.post('/setSearchTerm', (req, res) => {
let term = req.body.term;
app.locals.searchTerm = term;
twitterStream.destroy();
streamtweets();
});
const sendMessage = (data) => {
if(data.text.includes('RT')){
return;
}
socketConnection.emit("tweets", data)
}
}

nodejs: does a find query to select n last records need to run asynchronously

I am a newbie in nodejs. I want to fetch n last records from mongo and write them to a socket when a(n android) client connects.
I wrote some code and it was okay when I tested it on a vps i had, but, after moving to new vps a problem appeared.
When the first client connects to the socket, it does not get the records. However, if a second client connects to the socket the find query runs again and the first client can see the related emit, but, not for second client!
I added a log after the io.emit command and it runs for every client connecting.
I also added another emit that just sends a test text and it delivered to client as soon as he connected.
My code:
const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => {
res.send('Chat Server is running on port ......')
});
var mongoose = require('mongoose');
var ChatMessage = require('./chatmessage');
var chatsdb = "mongodb://localhost:27017/chatsdb"
mongoose.connect(chatsdb, {useNewUrlParser: true});
mongoose.connection.on('connected', function () {
console.log('Mongoose connected!')
});
mongoose.connection.on('error', function (err) {
console.log('Mongoose default connection error:' + err);
});
io.on('connection', (socket) => {
let userId = socket.id;
console.log('user ' + userId + ' connected');//it always run
io.emit('connected_message', {"message_text": "ok!" , "user_id":userId});//it always run
ChatMessage.find().sort({created_at:-1}).limit(10).exec(function (err, posts) {
let list_of_messages = [];
for (let i = 0; i < posts.length; i++) {
if (posts[i] != null) {
let message = new ChatMessage({"message_text": posts[i].message_text, "name": posts[i].name , "created_at": posts[i].created_at});
list_of_messages.push(message);
}
}
io.emit('last_fifty_message', list_of_messages);
console.log("list_of_messages:" + list_of_messages); //it always run
});
});
server.listen(50000, () => {
console.log('Node app is running on port 50000')
});
and it's ChatMessage class:
var mongoose = require('mongoose');
const Schema = mongoose.Schema;
var ChatMessageSchema = new Schema({
name : { type: String, require:true },
message_text : { type: String , require:true },
created_at : { type:Date , default: Date.now },
message_id : { type: Number , autoIncrement:true }
});
ChatMessageSchema.pre('save',function(next){
this.created_at = new Date();
console.log('this.created_at : '+this.created_at)
next();
});
var ChatMessage = mongoose.model('ChatMessage' , ChatMessageSchema);
module.exports = ChatMessage;
I don't understand. If find records is a long process, how is it logged but not emitted and why is it emitted for clients that have connected already?
Does it need to run asynchronously? Can I use async/await or callbacks or ...??
You are using io.emit, io.emit is sending to all connected clients
For emitting only to the connected socket use socket.emit
By the way, you should check if error Isn’t null

reconnecting to nodejs websocket on failure

This is my first practice after reading some tutorials and videos. Basically, I need message to be sent from the server (nodejs) to the client (Angular 6). At first tho, when client app is booted, it sends user id to the server for authentication. The server then will send data based on that user.
Now my problem is on first load and a few calls, the connection does work. But then on refresh or so, the connection drops. My client does console out "retrying" but it never succeeds. It works only if I manually restart the server and reload the client so a new connection could be established.
How can I maintain a fairly stable connection throughout the lifetime of a client? At times the readyState stays at 3 on the server i.e. connecting, which I am confused with because the client does try to reconnect...just fails.
My Server is simple. index.js (Tried to put it up on stackblitz but failed...would appreciate if someone can figure out the dependency file: nodejs websocket server)
const express = require('express');
const bodyParser = require('body-parser');
const pg = require ('pg');
var ws = require('./ws')
var app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(3000, function () {
console.log('We are running on port 3000!')
})
ws.js:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'error.log'})
]
});
var WebSocketServer = require("ws").Server,
wss = new WebSocketServer({
port: 40511
});
let data = {
'packet': ['amy1', 'amy2', 'amy3']
}
const mx = 2;
const mn = 0;
wss.on("connection", function(ws) {
ws.on("message", function(user) {
// client has called now. If the connection
// fails, the client does try to connection again and again -- no limit but it simply doesn't seem to have effect. When connecting, it simply sends user name
console.log("received: %s", user);
setInterval(function(){
var random = Math.floor(Math.random() * (mx - mn + 1) + mn);
if (ws.readyState == ws.OPEN){
ws.send(data['packet'][random]);
}
}, 3000);
});
});
My front end is: service.ts
import { Observable} from 'rxjs';
export class WebSocketService {
socket: WebSocket;
constructor() { }
initConnection(): void {
if(!this.socket){
this.socket = new WebSocket('ws://localhost:40511');
// when connection is open, send user id
this.socket.onopen = () => this.socket.send(2);
}
}
manageState() : Observable<any>{
const vm = this;
return new Observable(observer => {
this.socket.onerror = (e) => {
// close it
this.socket.close();
observer.next('web socket communication closed due to error')
};
this.socket.onclose = (e) => {
//socket closed for any reason
setTimeout(function() {
console.log('try to connect')
vm.initConnection();
observer.next('still trying')
}, 1000);
}
});
}
onMessage(): Observable<any> {
// when message arrives:
return new Observable(observer => {
this.socket.onmessage = (e) => {
console.log(e.data);
observer.next(e.data)
};
});
}
}
component.ts:
// initialize the connection
this.service.initConnection();
this.service.onMessage().subscribe(
data => {
// we have got data
console.log('data came ', data)
},
err => {
console.log("error websocking service ", err);
}
);
// track state of the communication, letting the service to reconnect if connection is dropped
this.service.manageState().subscribe(data => {
console.log(data);
});

Need help on using takeUntil() and Observable.fromEvent() Methods

I'm following this tutorial to create Reactive TCP server in Nodejs
here's code that i've been working on
const Rx = require('rxjs')
const net = require('net')
const uuid = require('uuid');
module.exports = () => {
const sockets = new Map();
const ids = new Map();
const GetSocket = _id => sockets.get(_id);
const GetId = _socket => ids.get(_socket);
const SetSocket = _socket =>{
_socket.setEncoding('utf8');
const _id = uuid();
sockets.set(_id, _socket);
ids.set(_socket,_id);
return _id;
};
const server = net.createServer({ allowHalfOpen: true });
const socketStream = Rx.Observable.fromEvent(server, 'connection');
const RemoveSocket = socket = () => {
console.log("connection closed && removing socket from Map");
const id = ids.get(socket);
sockets.delete(id);
ids.delete(socket)
};
const socketObservable = socket => SetSocket(socket) &&
Rx.Observable
.of({
action: 'CONNECTION',
socket: GetId(socket)
})
.merge(
Rx.Observable
.fromEvent(socket,'data')
.map(d=>{
try {return JSON.parse(d);}
catch (e) {
console.log(e);
return d;
}
})
.map(msg=>{
return Object.assign({action:msg,socket:GetId(socket)})
})
)
.takeUntil(Rx.Observable.fromEvent(socket, 'close').map(d=>{
console.log("!!!! Should remove !!!");
RemoveSocket(socket);
}));
const Print = ()=>{
//ids.forEach(id=> console.log(GetSocket(id)));
console.log("total connected socket : " + ids.size);
};
const startServer = port => server.listen(port) &&
socketStream
.flatMap(socketObservable);
return {startServer, Print , stop: () => server.close()};
};
and here's my test result(just sending test msg and connect/reconnect to server )
{ action: 'CONNECTION',
socket: '8989b581-dc54-479b-a8c0-870cc8103c5b' }
total connected socket : 1
{ action: { test: 1 },
socket: '8989b581-dc54-479b-a8c0-870cc8103c5b' }
total connected socket : 1
{ action: { test: 2 },
socket: '8989b581-dc54-479b-a8c0-870cc8103c5b' }
total connected socket : 1
{ action: 'CONNECTION',
socket: 'b868104b-d1cf-41c9-950f-472f63bac27a' }
total connected socket : 2
{ action: { test: 1 },
socket: 'b868104b-d1cf-41c9-950f-472f63bac27a' }
total connected socket : 2
{ action: 'CONNECTION',
socket: 'b9a579fe-3715-4952-aaf7-d7f64a0bea99' }
total connected socket : 3
Everything working fine till detecting socket close event by TakeUntil()
I tried using takewhile() by simply adding counter like this TakeWhile(cnt < 5)
and socket stream completed as expected.
this is my first attempt to make something with Node.js and feel like i'm missing something.
can anyone help me to understand why takeUntil() is not working here?
Thank you :)
So my confusion was from understanding 'close' and 'end' events
'end' event gets triggered when the client disconnected or server calls socket.end(..) when server receives FIN packet
and 'close' event gets called after socket.destroy()
if anyone wants to see all socket events in action, I recommend watching this video
#Brannon, Thank you for pointing out the right event usage and thank you, everyone, for helping me out with this!!
also just in case, anyone wants working TCP server code.
dependency : rxjs 5.5.0
const Rx = require('rxjs');
const net = require('net');
const uuid = require('uuid');
module.exports = () => {
const sockets = new Map();
const ids = new Map();
const GetSocket = _id => sockets.get(_id);
const GetId = _socket => ids.get(_socket);
const SetSocket = _socket =>{
_socket.setEncoding('utf8');
const _id = uuid();
sockets.set(_id, _socket);
ids.set(_socket,_id);
return _id;
};
const server = net.createServer({ allowHalfOpen: true });
const socketStream = Rx.Observable.fromEvent(server, 'connection');
const RemoveSocket = socket => {
const id = ids.get(socket);
sockets.delete(id);
ids.delete(socket)
console.log("[server.js] socket closed..");
};
const socketObservable = socket => SetSocket(socket) &&
Rx.Observable
.of({
action: 'CONNECTION',
socket: GetId(socket)
})
.merge(
Rx.Observable
.fromEvent(socket,'data')
.map(d=>{
try {return JSON.parse(d);}
catch (e) {
console.log(e);
return d;
}
})
.map(msg=>{
return Object.assign({action:msg,socket:GetId(socket)})
})
)
.takeUntil(Rx.Observable.fromEvent(socket, 'end')
.map(()=>RemoveSocket(socket)));
const Print = ()=>{
//ids.forEach(id=> console.log(GetSocket(id)));
//ids.clear();
console.log("total connected socket : " + ids.size);
};
const startServer = port => server.listen(port) &&
socketStream
.flatMap(socketObservable);
console.log("[server.js] Starts Started" );
return {startServer, Print , stop: () => server.close()};
};

Resources