Today I hit the wall while writing TCP server in node js ...
I need to make TCP server for windows and android app: Using just nodejs net module ...
there is app class in with is called server class that creates several Client instances. one on each connection ...
And in each of these client instances, I need a global var that is visible in "current" instance of client...and recursively in all other instances, that's been invoked in client class instance.
I need that global var for holding session content like vars, objects, and crypt keys ... for all instances called in Client class instance and so one... without passing values to every new instane ...
//app.js
var Server = require("./core/server/Server");
console.log('hi!');
console.dir("there is nothing to look at at the momment");
global.DEBUG = true;
global.NEW_GUID = require('uuid/v4');
var server = new Server()
server.start();
//server.js
const net = require('net');
const Client = require("./Client");
class Server{
constructor (port, address) {
this.port = port || 4484;
this.address = address || '127.0.0.1';
this.clients = [];
}
start(callback) {
let server = this;
server.connection = net.createServer((socket) => {
socket.setEncoding("UTF8");
let client = new Client(socket);
server.clients.push(client);
socket.on('error',(e)=>{ console.dir(e); })
socket.on('end', () => {});
});
this.connection.listen(this.port, this.address);
}
}
module.exports = Server;
//Client.js
//req some components
GLOBAL_VAR = {
login :"a",
sess_crypt: "encryption instance setuped in client",
socket:seocket
}
class Client {
constructor(socket) {
//some this vars }
async GetLogin() {
}
async GetData(d) {
}
StartDialog() {
}
serverHandler(){
console.log(`${this.name} connected.`);
//client.GetLogin();
this.socket.write("WELCOME\r\n")
this.socket.on("data", (d) => {
var ed = Buffer.from(d);
console.dir(ed.toString("UTF8"));
this.GetData(ed).then((r)=>{
if (r.cmd == "LOGIN") {
this.sth = new sth();
this.sth.sth(); // inside this you can have multiple calls of sth and i can't pass any value by parameter because of rest "old" js code what was running on RHINo Java server that i can't modifi to much
}
})
})
}
}
module.exports = Client;
///sth.js
//req some components
class sth extends other_staf{
constructor() {
this.login = GLOBAL_VAR.login
}
oninit(){
// do staf start next instance of sth()
}
sth(){
GLOBAL_VAR.socket.write(GLOBAL_VAR.sess_crypt("some request\r\n"))
this.oninit();
}
}
module.exports = sth;
You don't need global variables. You can try to do something like this, define data in Client so like so
var client = new Client();
client.data.name = "client 1";
or better pass it to contructor:
var client = new Client({name: "client 1"});
and in constructor use
function Client(options) {
this.data = options;
// ...
}
and pass client object to instances:
var instance = new instance(client);
and access:
instance.client.data.name;
if that box you have in proved image is the inside of the object then you can use this instead of instance and client.
Related
Here a stackblitz of the problem:
https://stackblitz.com/edit/angular-ivy-jvm8pn?file=src%2Fapp%2Fapp.component.ts,src%2Fapp%2FMyWebsocketService.ts,src%2Fapp%2Fapp.component.html
How the hell do you (delete / destroy / release) a WebSocket instance???
export class MyWebsocketService {
public url;
_constructor( URL:string ) {
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => { // blablabla }
this.ws.onmessage = () => { // blablabla }
this.ws.onclose = () => {
/////// I CANNOT for the life of me destroy that WebSocket instance from memory
/////// These are all the solutions online:
this.ws.onopen = null;
this.ws.onmessage = null;
this.ws.onclose = null;
this.ws.onerror = null;
this.ws.close();
this.ws = null;
delete this.ws;
setTimeout( _ => {
console.log('Reconnecting...');
this.connect();
}, 3000);
}
}
}
...
public websocket;
createWebsocketsServices() {
this.websocket = new MyWebsocketService('wss://whatever');
// HERE I'm changing the URL
this.websocket = new MyWebsocketService('wss://my-new-url');
////////// BUT THE FIRST WEBSET IS STILL IN MEMORY!!!!!!!
}
ngOnInit(): void {
this.createWebsocketsServices();
}
None of the solutions I found online actually resolve this issue
Understanding object creation and garbage collection of a NodeJS WebSocket server
WebSocket: How to automatically reconnect after it dies
How do you remove a native websocket handler when setting with ws.onmessage = myFunc; or ws.onopen = function(){}?
///////////// EDITED ////////////////////
"What makes you assume that the websocket is kept in memory?" A detail I forgot to mention is: in my real life application say I try to connect to 4 dummy URL and after I connect to the valid one... the ws server register 4 new connections not 1. They are still alive
My goals:
Try to connect to websocket
if can't connect try to reconnect every 3 seconds
Change the websocket url on runtime = (Destroy current ws instance + create new ws instance with new URL)
That's a hack that works for now
https://stackblitz.com/edit/angular-ivy-vjezvq?file=src/app/app.component.ts
It doesn't resolve the issue the websocket is still not released from memory but I can at least change URL
When replacing one MyWebsocketService with another one, you'll need to disconnect the former one. Otherwise the web socket stays open, continues to receive messages, and will retain in memory all the callbacks that are registered to events and the things they reference. Overwriting the websocket variable will do nothing to prevent this, it does not "destroy" the object or release its memory, it just removes one reference to the object which would allow the garbage collector to collect the object if it wasn't referenced elsewhere - but it still is referenced from the open socket.
You'll want to do
export class MyWebsocketService {
public url: string;
public ws?: WebSocket;
_constructor(url: string) {
this.url = url;
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => { // blablabla }
this.ws.onmessage = () => { // blablabla }
this.ws.onclose = () => {
this.ws = null;
}
}
disconnect() {
this.ws?.close();
}
}
then
createWebsocketsServices() {
this.websocket = new MyWebsocketService('wss://whatever');
this.websocket.connect();
// HERE I'm changing the URL:
this.websocket.disconnect();
this.websocket = new MyWebsocketService('wss://my-new-url');
this.websocket.connect();
}
Im trying to send an answer to my websocket-server from a component which does not contain the websocket. My Websocket server looks like this:
componentDidMount() {
var ws = new WebSocket('ws:// URL');
ws.onmessage = this.handleMessage.bind(this);
...
}
How can I pass the "var ws" to another class or component. Or is it possible to make the websocket globally accessable?
Thank you very much for any help!
I found a solution with help from this question in stackoverflow:
visit:
React native: Always running component
I created a new class WebsocketController like this:
let instance = null;
class WebsocketController{
constructor() {
if(!instance){
instance = this;
}
this.ws = new WebSocket('ws://URL');
return instance;
}
}
export default WebsocketController
And then in my other class where I need my websocket I just called it like this:
let controller = new WebsocketController();
var ws = controller.ws;
websocket connection
keep this code in some file, name it with .js extenstion. ex: websocket.js
var WebSocketServer = require("ws").Server;
var wss = new WebSocketServer({port:8100});
wss.broadcast = function broadcast(msg) {
console.log(msg);
wss.clients.forEach(function each(client) {
client.send(msg);
});
};
wss.on('connection', function connection(ws) {
// Store the remote systems IP address as "remoteIp".
var remoteIp = ws.upgradeReq.connection.remoteAddress;
// Print a log with the IP of the client that connected.
console.log('Connection received: ', remoteIp);
ws.send('You successfully connected to the websocket.');
ws.on('message',wss.broadcast);
});
In your app/website side. create .js file. Ex: client.js
var SERVER_URL = 'ws://127.0.0.1:8100';
var ws;
function connect() {
//alert('connect');
ws = new WebSocket(SERVER_URL, []);
// Set the function to be called when a message is received.
ws.onmessage = handleMessageReceived;
// Set the function to be called when we have connected to the server.
ws.onopen = handleConnected;
// Set the function to be called when an error occurs.
ws.onerror = handleError;
}
function handleMessageReceived(data) {
// Simply call logMessage(), passing the received data.
logMessage(data.data);
}
function handleConnected(data) {
// Create a log message which explains what has happened and includes
// the url we have connected too.
var logMsg = 'Connected to server: ' + data.target.url;
// Add the message to the log.
logMessage(logMsg)
ws.send("hi am raj");
}
function handleError(err) {
// Print the error to the console so we can debug it.
console.log("Error: ", err);
}
function logMessage(msg) {
// $apply() ensures that the elements on the page are updated
// with the new message.
$scope.$apply(function() {
//Append out new message to our message log. The \n means new line.
$scope.messageLog = $scope.messageLog + msg + "\n";
});
}
Please let me know if you face any issue with this code
How do I keep track of all the connected clients in socket.io?
I have tried this on the server:
let numSockets = 0;
io.on('connection', (socket) => {
io.sockets.emit('numSockets', ++numSockets);
socket.on('disconnect', () => {
io.sockets.emit('numSockets', --numSockets);
});
});
and this on the client:
const socket = io();
socket.on('numSockets', function (numSockets) {
console.log(numSockets);
});
It does print a number, but the number, however, if I open 2 windows, it shows 4 connected sockets.
Is this the correct way to do it?
What I want to achieve is to print a list of the connected sockets' ids in a sidebar on my website, and let the user set a username (instead of the automatically generated id) if they want to.
But before moving on to this, I will make sure I can keep track of the sockets in a correct way.
I don't think that io.sockets.emit(io.of('/').connected) is a good idea because it will send a hash of socket objects which is a lot of data :-)
You can try the following function:
function findUsersConnected(room, namespace) {
var names = [];
var ns = io.of(namespace || "/");
if (ns) {
for (var id in ns.connected) {
if(room) {
var roomKeys = Object.keys(ns.connected[id].rooms);
for(var i in roomKeys) {
if(roomKeys[i] == room) {
if (ns.connected[id].username) {
names.push(ns.connected[id].username);
} else {
names.push(ns.connected[id].id);
}
}
}
} else {
if (ns.connected[id].username) {
names.push(ns.connected[id].username);
} else {
names.push(ns.connected[id].id);
}
}
}
}
return names.sort();
}
which returns an array of users connected to a room in a namespace. If a socket has not socket.username property then socket.id is used instead.
For instance:
var usersConnected = findUsersConnected();
var usersConnected = findUsersConnected('myRoom');
var usersConnected = findUsersConnected('myRoom', '/myNamespace');
There's the Namespace#connected object that contains all sockets (keyed by their id) that are connected to a particular namespace.
To retrieve the socket id's of the default namespace (/):
let clientIds = Object.keys( io.of('/').connected );
(where io is the server instance)
As of today, socket.io implemented a function called fetchSockets() on server side to retrieve all sockets that are currently connected on the server-side. (Source : https://socket.io/docs/v4/server-instance/#fetchSockets)
You can then use it like this :
const io = require("socket.io")
async function retrieveSockets() {
const connectedSockets = await io.fetchSockets()
}
As far as i tested, you can even execute action on sockets thanks to that, like emitting events, joinings rooms...etc
const count = io.engine.clientsCount;
This seems like a more inclusive approach and may be the same count of Socket instances in the main namespace as below. But depending on namespaces and usage they could be different.
const count2 = io.of("/").sockets.size;
https://socket.io/docs/v4/server-api/#engineclientscount
I believe it is Object.keys(io.sockets.connected).length. Check server api http://socket.io/docs/server-api/
I'm trying to give the socket object to my 'ConnectionHandler' class, but when using this socket object it gives this error: 'cannot read property socket of undefined socket.io'.
Server class:
Server.prototype.handleConnections = function ()
{
this.queueTime = 15; // Queue time in seconds
var that = this; // Create a global variable of the server object
// On incoming connection
this.io.on('connection', function (socket) {
console.log('connection incoming...'); // Log a message to the server console
// When a client tries to join the queue
socket.on('client_join_queue', function (username) {
// Check if the username is valid
if (! (username.length < 3)) {
var newPlayer = new player(username);
var connectionHandler = new connectionHandling(socket, that, newPlayer);
that.connections.push(connectionHandler);
}
});
});
}
ConnectionHandler class:
'use strict';
var ConnectionHandler = function (_socket, _server, _player)
{
this.socket = _socket;
this.server = _server;
this.player = _player;
this.server.queueHandler.addPlayer(this.player);
this.server.connections[0].socket.emit('player_joined_queue', this.player, this.server.queueHandler.getQueue().length);
var that = this;
socket.on('disconnect', function () {
console.log("user disconnected");
console.log("queue:", that.server.queueHandler.getQueue());
});
}
module.exports.ConnectionHandler = ConnectionHandler;
I've absolutely no idea what I'm doing wrong.
What fljs was saying, you are emitting an event called 'player_joined_queue'.
But you are listening for the event 'client_join_queue'. You need to listen for the event with the same name you are emitting. So you would need to change one or the other. for example,
socket.on('player_joined_queue', function (username) {
...
I have troubles for creating a module which exposes functionalities for my Socket.IO library:
const sio = require('socket.io');
module.exports = function(server) {
const io = sio(server);
return {
register: function(namespace) {
let nsp = io.of(namespace);
nsp.on('connect', function(socket) {
// ...
}
}
}
}
The problem is now how do I make use of this in other modules? In my app.js
I create the server with Express and can instantiate the module with require('./mysocketio')(server) but not in other modules because server is not available there. What's a nice way to resolve these circular dependencies?
Well you can achieve those in various ways, like:
setting objects to global namespace. (altering global needs care)
Use module.exports and require the object in the other files. (can lead to circular dependency issues if not done properly)
pass the instance as arguments to the controllers, while requiring them in routes.
myModule.js Module which exposes functionalities of your Socket.IO library
const sio = require('socket.io');
module.exports = function(server) {
const io = sio(server);
return {
register: function(namespace) {
let nsp = io.of(namespace);
nsp.on('connect', function(socket) {
// ...
}
}
}
}
FLow 1: set the module in global namespace.
app.js
var app = require('express').createServer();
var io = require('./myModule')(app);
global._io = io;
app.listen(80)
controller.js
module.exports = function(io){
var that={};
/*
* Private local variable
* made const so that
* one does not alter it by mistake
* later on.
*/
const _io = global._io;
that.myAction = function(req,res){
_io.register('newRoom');
res.send('Done');
}
return that;
}
Flow 2: passing module as arguments.
app.js
var app = require('express').createServer();
var io = require('./myModule')(app);
require(./router.js)(app,io);
app.listen(80);
router.js
/*
* Contains the routing logic
*/
module.exports = function (app,io) {
//passing while creating the instance of controller for the first time.
var controller = require("./controller")(io);
app.get('/test/about',controller.myAction);
};
controller.js
module.exports = function(io){
var that={};
const _io = io;
that.myAction = function(req,res){
_io.register('newsRoom');
res.send('Done');
}
// everything attached to that will be exposed
// more like making public member functions and properties.
return that;
}
Flow 3: Setting io to global. Thus no need to pass server every time.
app.js
var app = require('express').createServer();
require('./myModule')(app);
require(./router.js)(app);
app.listen(80);
controller.js
// no need to pass the server as io is already initialized
const _io = require('./myModule')();
module.exports = function(io){
var that={};
that.myAction = function(req,res){
_io.register('newsRoom');
res.send('Done');
}
return that;
}
myModule.js
module.exports = function( server ) {
const _io = global._io || require('socket.io')(server);
if(global._io === undefined){
//initializing io for future use
global._io = _io;
}
return {
register: function(namespace) {
let nsp = _io.of(namespace);
nsp.on('connect', function(socket) {
// ...
}
}
}
}
Probably, the cleanest way is to pass is as arguments to the controllers, while requiring them in routes. Although 3rd flow seems promising but one should be care full while altering the global namespace.
It's not really a circular dependency; It's just that your module a) depends on another module that's not globally available and b) your module is presumably used in many places in your code.
Global
A possible solution (with downsides), is to just load your module once, and attach it to a global:
global.mysocketio = require('./mysocketio')(server);
This allows you to access global.mysocketio anywhere in your project, once it has been loaded. This is a construction that I personally use for an own logger construction; My logger is used in many places around my code, so I just keep it attached to global.log.
However, usage of globals is a bit dirty; It gives problems with namespace-separation (what is somewhere some code decides to use global.mysocketio itself), and it creates an 'invisible' dependency; Other code just assumes that a certain global will exist, and it's not that easy to find these dependencies.
Export
A nicer solution is to just pass the variable wherever needed. There are many ways to do this. I understand that your app.js doesn't have the server variable available, but it surely is including your express-code in some way. If you need the 'server' or 'mysocketio' available from app.js, just export it from your module where you are creating 'server'. Like:
module.exports.expressServerVar = server;
Just my 2 cents; Do you strongly disagree with me or am I missing something important? Let me know!
I'd use a factory or dependency injection. You could use something like jimple.
But here's an example without using any external dependencies. This is by no means the best code example but it should hopefully get the point across. I'd still recommend using jimple rather than this.
// app.js
var express = require('express');
var app = express();
var factory = require('./factory.js');
factory.setExpress(app); // This could also be done in the factory constructor. Or you could instanciate your express app in the factory.js class.
// factory.js
var socketIoModule = require('./your-socket-io-module.js')
function Factory() {
}
Factory.prototype.setExpress = function(app) {
this.app = app;
}
Factory.prototype.getSocketIOModule = function() {
return socketIoModule(this.app);
}
// By exporting it this way we are making it a singleton
// This means that each module that requires this file will
// get the same instance of factory.
module.exports = new Factory();
// some code that needs socket io module
var factory = require('./factory.js');
function() {
var socketIo = factory.getSocketIOModule();
socketIo.doStuff();
}
Approach that I use in my applications is exposing server and io instances from start script and reusing them in modules
// Setup servers.
var http = require('http').Server(app);
var io = require('socket.io')(http);
// Setup application.
require('./server/app')(app, express, io);
// Start listening on port.
http.listen(configs.PORT, function() {
console.log("Listening on " + configs.PORT);
});
Inside your modules you can use io instance to setup event handlers or emit events, something like this
module.exports = {
contest: function(io, contest) {
var namespace = io.of('/' + contest.id);
namespace.on('connection', function(socket) {
socket.on('word', function(data) {
...
});
});
}
};
For your sample
I would put this part in app.js or in js file that is used to start server
const sio = require('socket.io');
const io = sio(server);
and will have Socket.IO module like this
module.exports = function(server, io) {
return {
register: function(namespace) {
let nsp = io.of(namespace);
nsp.on('connect', function(socket) {
// ...
}
}
}
}
My sample
https://github.com/gevorg/typeitquick/blob/master/web.js
https://github.com/gevorg/typeitquick/blob/master/server/contest.coffee
https://github.com/gevorg/typeitquick/blob/master/server/io.coffee