i'm a noob of node.js and i'm following the examples on "Node.js in action".
I've a question about one example :
The following code implements a simple chat server via telnet. When i write a message, the script should send message to all connected client.
var events = require('events');
var net = require('net');
var channel = new events.EventEmitter();
channel.clients = {};
channel.subscriptions = {};
channel.on('join',function(id,client){
this.clients[id] = client;
this.subscriptions[id] = function(senderId,message){
if(id != senderId){
this.clients[id].write(message);
}
};
this.on('broadcast',this.subscriptions);
});
var server = net.createServer(function(client){
var id = client.remoteAddress+':'+client.remotePort;
client.on('connect',function(){
channel.emit('join',id,client);
});
client.on('data',function(data){
data = data.toString();
channel.emit('broadcast',id,data);
});
});
server.listen(8888);
But when i try to connect via telnet and send a message it doesn't work.
Thanks
A couple issues I noticed. See the comments in the code.
var events = require('events');
var net = require('net');
var channel = new events.EventEmitter();
channel.clients = {};
channel.subscriptions = {};
channel.on('join',function(id, client) {
this.clients[id] = client;
this.subscriptions[id] = function(senderId,message) {
if(id != senderId)
this.clients[id].write(message);
};
//added [id] to "this.subscriptions"
//Before you were passing in the object this.subscriptions
//which is not a function. So that would have actually thrown an exception.
this.on('broadcast',this.subscriptions[id]);
});
var server = net.createServer(function(client) {
//This function is called whenever a client connects.
//So there is no "connect" event on the client object.
var id = client.remoteAddress+':'+client.remotePort;
channel.emit('join', id, client);
client.on('data',function(data) {
data = data.toString();
channel.emit('broadcast',id,data);
});
});
server.listen(8888);
Also note: If a client disconnects and another client sends a message then this.clients[id].write(message); will throw an exception. This is because, as of now, you're not listening for the disconnect event and removing clients which are no longer connected. So you'll attempt to write to a client which is no longer connected which will throw an exception. I assume you just haven't gotten there yet, but I wanted to mention it.
Related
I am trying to have a module contain the information for different roles for a game but whenever I try and receive the data from the variables I create, it comes as undefined and/or I get errors saying something similar to saying the variable inside the data doesn't exist (such as the role's name).
I've seen a bunch of tutorials do similar but I can't seem to figure out what I've done wrong.
My index file.
var express = require('express');
var app = express();
var serv = require('http').Server(app);
var RoleList = require('./_modules/RoleList');
var Socket = function() {}
Socket.list = {};
serv.listen(process.env.PORT || 4567);
console.log("Server started.");
var io = require('socket.io')(serv, {});
io.sockets.on('connection', function(socket) {
socket.id = Math.random();
Socket.list[socket.id] = socket;
console.log('Player Connected');
console.log('Role Name: ' + RoleList.testRole.roleName);
socket.on('disconnect', function() {
delete Socket.list[socket.id];
console.log('Player Disconnected');
});
});
RoleList Module
var Role = function() {
var self = {};
var roleName = 'TestingRoleName';
return self;
}
modules.exports = {
roleTest: Role
}
But upon running the server I get the results.
Server started.
Player Connected
T:undefined
instead of
T:TestingRoleName
Is anyone able to help with this? I'd appreciate that so much :D I am likely doing something completely wrong but I can't seem to find an answer anywhere.
This i because Role is a function not Object. You need to call function to return self. And you should set roleName as property of self
var Role = (function() {
var self = {};
self.roleName = 'TestingRoleName';
return self;
})();
Or you can change Role to this.This is the way I recommend
var Role = {
roleName:'TestingRoleName'
}
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
I've the following code working in my server-side, it's all ok. But, I want to keep the same connection between n tabs, because when I open a new tab, looks like I've disconnected from the first tab... So, how can I keep the same connection?
client.js
socket.emit("connected", {user: inputUser.val()};
app.js
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
users[socket.user] = socket;
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
users[userMsg].emit("typing", {user: socket.user});
}
});
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
delete users[socket.user];
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
Update:
The typing event above works fine... So I tried the typing event according to the answer:
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// add this socket to the Set of sockets for this user
if (!users[socket.user]) {
users[socket.user] = new Set();
}
users[socket.user].add(socket);
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
users[userMsg].emit("typing", {user: socket.user});
}
});
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// remove socket for this user
// and remove user if socket count hits zero
if (users[socket.user]) {
users[socket.user].delete(socket);
if (users[socket.user].size === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
But it is giving the following error:
users[userMsg].emit("typing", {user: socket.user});
^
TypeError: users[userMsg].emit is not a function
Update²:
To fix the typing event error, I just changed to:
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
for(let userSet of users[userMsg]) {
userSet.emit("typing", {user: socket.user});
}
}
});
There is no simple way to share a single socket.io connection among multiple tabs in the same browser. The usual model for multiple tabs would be that each tab just has its own socket.io connection.
The opening of a new tab and a new socket.io connection should not, on its own, cause your server to think anything was disconnected. If your code is doing that, then that is a fault in your code and it is probably easier to fix that particular fault.
In fact, if you want to explicitly support multiple tabs and be able to recognize that multiple tabs may all be used by the same user, then you may want to change your server side code so that it can keep track of multiple sockets for a single user, rather than how it is currently coded to only keep track of one socket per user.
If your server code is really just trying to keep track of which users online, then there's probably an easier way to do that by referencing counting each user. I will post a code example in a bit.
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// increment reference count for this user
if (!users[socket.user]) {
users[socket.user] = 0;
}
++users[socket.user];
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// decrement reference count for this user
// and remove user if reference count hits zero
if (users.hasOwnProperty(socket.user)) {
--users[socket.user];
if (users[socket.user] === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
If you need the users object to have the socket object in it, then you can change what is stored in the users object to be a Set of sockets like this:
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// add this socket to the Set of sockets for this user
if (!users[socket.user]) {
users[socket.user] = new Set();
}
users[socket.user].add(socket);
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// remove socket for this user
// and remove user if socket count hits zero
if (users[socket.user]) {
users[socket.user].delete(socket);
if (users[socket.user].size === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
For anyone still having this issue. here is how i fixed it.
let me explain.
once the page refreshes or a new tab is opened, socket dosen't really care so it opens a new connection every time . this is more of a advantage than disadvantage. the best way to tackle the issue is on the server side, once a user logs in with his or her user name , you can send that name along with the query options on the client so it can be used as a unique identifier. in my case i used a token
this.socket = io.connect(`${environment.domain}` , {
query: {token: this.authservice.authToken}
});
then on the server side you can create an empty array to a key and an array of values. the username of the user will be used as a key and the corresponding array of socket as the value. in my own case like i said i used a token
const users = [ ]
socket.nickname = (decoded token username);
users[socket.nickname] = [socket];
then you can perform a simple logic to check if a user already exists in an array, if it does, push the new socket to the array of the user
if ( user.username in users) {
console.log('already exists')
users[user.username].push(socket);
}
if it dosent, just create a new key and add the socket as the key.(make sure its an array because a user can always refresh or open a new tab with the same account and you dont want the chat message to deliver in one tab and not deliver in another)
else {
socket.nickname = username;
users[socket.nickname] = [socket];
}
then to emit a message you simply loop through the array and emit the message accordingly. this way each tab gets the message
socket.on('chat', (data) => {
if (data.to in users) {
for(let i = 0; i < users[data.to].length; i++) {
users[data.to][i].emit('chat', data)
}
for(let i = 0; i < users[data.user].length; i++) {
users[data.user][i].emit('chat', data)
}
}
})
you can add a disconnect logic to remove the socket from the users array too to save memory, so only currently open tabs acre active and closed tabs are removed. i hope it solved your problem
My solution is joining socket to a room with specific user Id.
io.on('connection', async (socket) => {
socket.join('user:' + socket.handshake.headers.uid) // The right way is getting `uid` from cookie/token and verifying user
})
One advantage is sending data to specific user (sending to all tabs)
io.to('user:' + uid).emit('hello');
Hope it's helpful!
I belive the best way is create a channel for the user and unique it by their ID, so, when you need to receive or send something you use the channel and every socket connected to it will receive.
Another solution is to save the flag to localStorage and use eventListener to change localStorage.
Do not connect when another connection exists.
and save message in local storage for send with master tab.
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 am a trying to use socket.io and node.js like this :
The browser sends an event to socket.io, in the data event I call another server to get some data, and I would like to send back a message to the browser using a socket.emit.
This looks like that :
socket.on('ask:refresh', function (socket) {
const net = require("net");
var response = new net.Socket();
response.setTimeout(1000);
response.get_response = function (command, port, host) {
this.connect(port, host, function () {
console.log("Client: Connected to server");
});
this.write(JSON.stringify({ "command": command }))
console.log("Data to server: %s", command);
};
response.on("data", function (data) {
var ret = data.toString();
var tmp_data = JSON.parse(ret.substring(0, ret.length - 1).toString());
var data = new Object();
var date = new Date(tmp_data.STATUS[0].When * 1000 );
data.date = date.toString();
socket.emit('send:refresh', JSON.stringify(data) );
});
response.get_response("version", port, host);
});
};
The thing is that I cannot access "socket.emit" inside response.on.
Could you please explain me how I can put a hand on this ?
Thanks a lot
You appear to be overwriting the actual socket with the one of the callback parameters:
socket.on('ask:refresh', function(socket) {
// socket is different
});
Change the name of your callback variable, and you won't have this problem.