I'm writing a two player card game, (say it's regular poker for simplicity) in Node.js and Express js. There's a couple of things I'm having trouble with. First, how do I make sure that there are only 2 players that can access an instance of the game, and is it possible to have them reconnect if they lose the connection? Second, how do send a message from the server to the client? I can send it within the "socket.on" listener call, but within the normal scope of the program I can't get it to work.
var socket = io.listen(app);
socket.on('connection', function(client){
player++;
if(player <= 2) {
var messageout = "player " + player + " connected";
client.broadcast(messageout);
client.on('message', function(data){console.log(data); })
client.on('disconnect', function(){console.log('disconnected');})
}
else {
socket.end;
}
});
I'm having trouble conceptually what's going on here and how to approach the problem. For example, do I do it all with sockets? Or do I return a web page with the updated state of the game (cards, bets, etc.) every turn?
First, how do I make sure that there
are only 2 players that can access an
instance of the game?
Create an array of instance objects.
When a new player joins either create a new instance and set them as player1, or add them to an existing instance as player two.
var instances = [];
function Instance () {
return {
name = 'game' + instances.length + 1,
gameVariables = defaults,
player1 = null,
player2 = null,
player1UUID = UUID(),
player2UUID = UUID()
}
}
Is it possible to have them reconnect
if they lose the connection?
If you send each player a UUID when they initially connect you can have them use it to authenticate when they reconnect.
How do I send a message from the
server to the client?
client.send({ gameState: gameState() });
of if you've saved the client into an object: instances[ 'game1' ].player1.send( data );
I do it all with sockets?
I would deal with all dynamic interactions with web sockets.
Do I return a web page with the updated state of the game (cards, bets, etc.) every turn?
I wouldn't send html over web sockets. Instead send json and use a client side template to render it.
Try now.
// server
var nowjs = require("now")
var everyone = nowjs.initialize(app);
everyone.connected(function() {
if (users.length < 2) {
users.push(this);
}
});
everyone.now.sendMessage = function(message, cb) {
gameLogic(message);
users.forEach(function(user) {
user.update(gameLogic.getState());
});
};
//client
$("#action").click(function() {
now.sendMessage($(this).data("action"));
});
now.update = function(data) {
// update UI
};
Related
Question based on this answer: https://stackoverflow.com/a/18650183/4478897
I tried to find this solution but nothing seems to work in the way that I need.
Clustering expressjs and socket.io we can share sessions using redis and send io messages inside io world (io.sockets.on('connection',...). The problem is if we want to send the message (or use a simple socket.join/leave) inside the expressjs world (route.get/post).
If we are not using clusters we can atach the client socket object to the express request object (or simply export the io object) and then use it at any time on any GET/POST route.
At the other hand, if we are clustering and use the mentioned method to get the socket object inside the expressjs world, sometimes the socket object is undefined because the socket object for this client is initialized at other worker.
Some example flow:
Client connects to http://localhost and worker 1 handles this request.
After the page is loaded, the client connects to socket.io. Worker 2 handles this connection.
Client do a POST and again worker 1 or worker X handles this request.
In this case when the client do the POST, only the worker 2 knows the socket object for this client. So this will get an undefined socket object.
So, the question:
How can we get the client socket object from any worker to reuse it on expressjs request object.
Maybe my code is wrong but is almost like the link to the answer mentioned above.
NOTEs
Don't want to use some kind of proxy.
Don't want to migrate to other libraries (expressio, sockjs...)
Sorry for my English :)
Using last nodejs, socket.io, expressjs, socket.io-redis, redis... versions
Don't hesitate to ask something!
UPDATE 1
Possible solution but still need to test it. Dont know if this is a really good: solution.
UPDATE 3: Working code on my own answer
UPDATE 2
Like update 1 but using https://nodejs.org/dist/latest-v5.x/docs/api/cluster.html#cluster_event_message
remoteJoin and remoteLeave methods were added in socket.io-redis 3.0.0:
io.adapter.remoteJoin('<my-id>', 'room1', function (err) {
if (err) { /* unknown id */ }
// success
});
io.adapter.remoteLeave('<my-id>', 'room1', function (err) {
if (err) { /* unknown id */ }
// success
});
Note: The implementation looks a lot (hopefully?) like the answer above.
Well finally tried the code and it works (with some misspells modifications and other things) but i'm sure that needs to be a better code somewhere. So i'm open to more answers!
This code is part of my socket.io module when authorize the client socket and some other stuff...
var redis = require("redis");
var redisPub = redis.createClient();
var redisSub = redis.createClient();
var PubSubChannel = "clusterChannel";
// Function that checks if this worker knows the socket object of this socketId.
// If not, publish the message to all the other sockets (workers)
io.socketDo = function (type, socketId, roomName) {
if (typeof io.sockets.connected[socketId] != "undefined") {
if (type === "join") {
return io.sockets.connected[socketId].join(roomName);
}
if (type === "leave") {
return io.sockets.connected[socketId].leave(roomName);
}
} else {
redisPub.publish(
PubSubChannel,
JSON.stringify({
type: type,
socketId: '' + socketId,
roomName: roomName
})
);
}
};
// Subscribe to some channel
redisSub.subscribe(PubSubChannel);
// When this worker receive a message from channel "PubSubChannel" checks
// if it have the socket object for this socketId and do the operation
redisSub.on("message", function (channel, data) {
data = JSON.parse(data);
var type = data.type;
var socketId = data.socketId;
var roomName = data.roomName;
if ((type === "join" || type === "leave") && channel == PubSubChannel){
if (typeof io.sockets.connected[socketId] != "undefined") {
if (type === "join") {
return io.sockets.connected[socketId].join(roomName);
}
if (type === "leave") {
return io.sockets.connected[socketId].leave(roomName);
}
}
}
});
Then just simply export the module and attach it to your expressjs request => req.io = io
// req.session.socketId value is fetched on "io.sockets.on('connection', function(socket) {"
// by express to socket.io using redis shared sessions
app.get('/', function (req, res) {
req.io.socketDo('join', req.session.socketId, 'someRoomToJoin');
// IT WORKS!
req.io.sockets.in('someRoomToJoin').emit('text');
req.io.socketDo('leave', req.session.socketId, 'someRoomToLeave');
res.send('Hello World!');
});
I create a server with Node.js:
var net = require('net');
var PORT = 8181;
var server = net.createServer(
function(socket) {
console.log(this.address());
socket.on('data', function(data) {
var msg = data.toString().replace(/\n$/, '');
console.log('got: ' + msg);
});
process.stdin.on('readable',
function() {
var chunk = process.stdin.read();
if (chunk !== null) {
socket.write(chunk);
}
}
)
socket.write('heyyo\n');
}
)
Now, when multiple connections are coming in, this server sends out the typed in line only to the first connection.
I have two questions:
what is a standard way to handle this, i.e. to store the incoming sockets into an array?
exactly what happens that causes the readable event not to reach the other connections' callback function?
I would highly recommend using a library like socket.io. It makes handling connect/disconnect as well as placing sockets in rooms very simple. Additionally you can get the full list of available rooms and connected sockets through the adapter class it offers. A functional example is available in the docs.
I am using nodejs + socket.io and have chat. Every user, when enters chat, get some data to his socket. For example:
module.exports = function(io) {
var chat = io.of('/chat').on('connection', function (socket) {
socket.on('start-chat', function(data) {
socket.user_name = data.name;
}
});
}
Question: How one user can change socket property of other? For example, i need to change others user socket.user_name, having his socket.id
You can get access to the connected clients and filter them for what ever criteria you need. IIRC you can also access them directly by ID if you happen to have the id with io.sockets.sockets[socket_id]
Another approach is to keep your own record of session. This means you can index using a key that you'd determine your self on each connection. An example:
var clientConnections = {};
sio.on('connection', function (socket) {
var key = <something unique, maybe based on socket.handshake data>;
clientConnections[key] = socket;
}
You can then just access the socket reference else where via the clientConnections hash: clientConnections[<some key].
Once you have that reference you should be able to manipulate the socket as if it was the subject of your event callback.
I want to save some data on the socket, server side, so whenever the client emits any data to the server, I want that data to be available!
One use case can be storing a token on the socket. When the client is connecting for the first time, it will emit the token, if it has one, or it will show the login page and then the login data will be sent to the server. Whichever one it is, I want to store the token on the server, so that every request after that doesn't need to specify the token.
Later, I'll use RedisStore, so all the data will be accessible all the servers running the app.
My only question is, where do I store the data on the socket so it's associated with that client?
on http://socket.io/#how-to-use
scroll to: Storing data associated to a client
use socket.set and socket.get to set and get data asynchronously
I'm suffering from the same question and guessing what's going on with an example code from socket.io on version 4.x
In the example, They use middleware(use function to register a middleware)
namespace.use((socket, next) => {
// get data from client
const sessionID = socket.handshake.auth.sessionID;
const {userId, username} = yourFunction();
// set socket specific data
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
next();
});
Middlewares are executed when a socket is connected with a server.
and you can use the data afterward
note - Socket.IO reference tells use socket.data for this purpose
namespace.on('connection', socket => {
socket.emit("join", `${socket.username} has been joined`);
})
If you use multiple servers, then you have to keep in mind that the data is only valid for the server
On multiple server environment, You need a single source of data which will be used by socket servers.
namespace.use(async (socket: Socket & { sessionID?: string, userID?: string, username?: string }, next) => {
const sessionID = socket.handshake.auth.sessionID; // [socket.handshake][4]
// or other [socket related attributes][4]
if (sessionID) {
// you have to implement a function to save and retrive session info
const session = await someFunctionToRetrieveSession(sessionID);
if (session) {
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
return next();
}
}
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid username"));
}
socket.sessionID = randomId();
socket.userID = randomId();
socket.username = username;
next();
});
and one more thing as I understood the namespace.use function is called only for the namespace if your client use other namespace then default then default('/') use function will not be called.
//client side
io("/chat");
...
//server side
io.use() // == io.of('/').use() will not be called
io.of('/chat').use() // only will be called
Thanksfully the author of the example implemented a sessionStorage using redis
refer to this example code
with this info, I guess socket.io server saves sockets' info in memory and set a property of a socket will be saved and when the socket comes later the server retrives the socket and it's related data. but because it happens on memory so you can't share the info among other servers that's why you have to find a way to share the data with other servers(eg. redis)
You can save the data on the global variables when you dont want to use any database
var globalVariable = {};
io.sockets.on("connection", function (socket) {
socket.on("save-client-data", function (clientData) {
var clientId = clientData.clientId;
globalVariable[clientId] = JSON.parse(clientHandshakeData);
});
socket.on("get-client-data", function (clientId) {
var clientData = globalVariable[clientId];
socket.emit("get-client-data", JSON.stringify(clientData));
});
});
This worked for my scenario, however I'm not aware of the performance implications.
I am writing a multi-player game using processing.js, node.js and socket.io.
Question #1:
In the client, I use p5.js to create a class Ball.
I want the server to send parameter to create an array using that class (balls.push(new Ball(x, y));), so every client can have a bunch of balls moving on the canvas.
I know I should use socket.io to emit the parameter to the client but i have no clue.
Normally the array is created inside the setup function inside p5...so how could socket do that?
Question #2:
How could the client send the mouseX and mouseY to the server? And then how could the server send back others' mouseX and mouseY to every client?
I try to make p5.js into normal js like this:
(function () {
"use strict";
function sketchProc(processing) {
var p=processing,
var ...,
var ...;
function ball(){...}
p.setup=function(){}
p.draw=function(){}
}
var canvas = document.getElementById("canvas1"),
p = new Processing(canvas, sketchProc);
}());
But i don't know if this helps...
The balls should be passed to the client only in the connection event. That's where node's beauty lies, code sharing.
To create a socket server, you can use either node's native Net module or Socket.io
On the server:
var server = net.createServer();
server.listen(PORT, HOST);
server.on('connection', function(socket) {
socket.write({ type: "BallsArray", data: BallsArray });
});
On the client, you use WebSockets with Socket.io:
var client = io.connect('http://localhost:3000');
var BallsArray = null;
client.on('message', function(msg) {
if(msg.type == "BallsArray")
BallsArray = msg.data;
});