Reputation: 101
I want the server to start listening to a certain socket from the client only after the client sends a message on another socket. I tried composing my code like that:
socket.on('listentome', function() {
socket.on('actualmessage', function() {
...
});
});
However it did not work. Any suggestions?
Upvotes: 2
Views: 2228
Reputation: 101
I have everything running smoothly, here is my code. Note that the playerMovementListener function is the one that installs eight socket.on-s that listen to that player only, as the player sends their ID with their movements.
io.on('connection', function(socket) {
//generate a user ID//
var userID = makeID();
//check if user is trying to connect from the same IP... if so send them out//
for(var i = 0; i < users.length; i++) {
if(users[i].IP == socket.handshake.address) {
io.sockets.emit(socket.id, 'You can only play one instance of the game!');
return;
}
}
//send the user's ID to the user//
io.sockets.emit(socket.id, userID);
//add the users to the array of connected users//
users.push({
'IP' : socket.handshake.address,
'ID' : userID,
});
//if user queues up for game (they send their ID when they queue up)...
//it is wise to check if their ID exists in the list of connected users to avoid DDOS, but I haven't implemented that yet
socket.on('queueup', function(data) {
//if there are already users playing...
if(pairs.length > 0) {
//get the index of the last pair...
var pairIndex = pairs.length-1;
if(pairs[pairIndex].length < 2) {
//if pair is not full (means that other user is queued and waiting for opponent), add the user to the pair and start the game//
pairs[pairIndex].push({
'ID' : data,
'x' : 850,
'y' : 850,
'mx' : 0,
'my' : 0,
'vermove' : 0,
'hormove' : 0,
});
//add the listener for this user...
playerMovementListener(socket, pairs[pairIndex][1].ID, pairIndex, 1);
//update and send coordinates 60 times per second..
setInterval(function() { runGame(pairs[pairIndex][0], pairs[pairIndex][1], pairIndex); }, 1000/60);
//send the ready message to the users in the pair..
io.sockets.emit(pairs[pairIndex][0].ID + ' ready', 'go');
io.sockets.emit(pairs[pairIndex][1].ID + ' ready', 'go');
} else {
//however if pair is already full, create new pair, add user to it, add listener for user and wait for opponent...
var pair = [];
pair.push({
'ID' : data,
'x' : 150,
'y' : 150,
'mx' : 0,
'my' : 0,
'vermove' : 0,
'hormove' : 0,
})
pairs.push(pair);
playerMovementListener(socket, pairs[pairIndex+1][0].ID, pairIndex+1, 0);
}
} else {
//if no users are playing, add user to first pair, add listener and wait for opponent...
var pair = [];
pair.push({
'ID' : data,
'x' : 150,
'y' : 150,
'mx' : 0,
'my' : 0,
'vermove' : 0,
'hormove' : 0,
});
pairs.push(pair);
playerMovementListener(socket, pairs[0][0].ID, 0, 0);
}
});
});
This code works as intended. At this point I just need to remove the listeners & clear the intervals for the game functions whenever a player disconnects or leaves the game.
What did not work was trying to install both players' listeners after the second player connects (instead of installing the first one's listener when they have connected, even if they aren't in a game yet... which worked). I'm not even sure why, but the point is that I have it working.
Upvotes: 1
Reputation: 708146
So, your proposed design:
socket.on('listentome', function() {
socket.on('actualmessage', function() {
...
});
});
is really just a bad design, fraught with problems. Here are some of the problems:
Every time you get a listentome
message, you add yet another event handler for actualmessage
and unless you remove them after they fire, they will accumulate and you will process each received actualmessage
more than once.
An event-driven architecture (which socket.io is) is not well suited for stateful sequences of messages which is what you are proposing with these sequenced messages. In fact, to program is reliably, it requires a bunch of extra code. It is really just better to fix the design to not require this and be more compatible with an event-driven architecture.
If you ever have more than one sequence of listentome
and actualmessage
in flight at the same time, you can't tell which actualmessage
message belongs with which listentome
first message and any of the solutions to the first two problems above are likely to double process the first one that arrives and not process the second one at all. So, this architecture just doesn't work at all if you ever have more than one listentome
sequence in flight at the same time (it's essentially a concurrency problem).
So, how to fix this? You don't give us a whole lot of context for the actual problem you're trying to solve to let us help you rearchitect it, but I'll give you as many ideas as I can.
First off, in an event driven architecture, you generally want to install the event handlers once so they are always in place. You can then use state to decide what to do with a message when it arrives.
Second, you'd rather not need state between requests at all on the server in order to carry out some operation. So, you'd rather whatever request is being sent is just complete all by itself so you don't need a sequence of events to accomplish something. As an analogy, with an http POST, you don't send each field of the form one at a time in separate requests and then somehow tell the server you're done and have it process everything. Instead, you gather up everything that needs to be sent and you send it in one http POST, the server processes everything and sends one response. Everything is stateless and a lot simpler.
I am creating a multiplayer game where users play in pairs. Every user gets assigned a unique ID by the server, then gets sent it and has to include it to each socket emission for validation (to avoid hacking). When a user queues up, they send a message (first socket.on) containing their ID, etc. When they are paired with a user (which happens immediately if a user is already waiting, hence the nested socket.on) they have to get sent into a game and only then can I start the listeners for the player's movements, etc (the second socket.on).
You don't show us your actual code, but it may be simpler to just add the various listeners on the socket for player's movements, etc... when the user first connects and just have each handler call one common function at the start of the handler that checks to see if the socket is validly connected in a game or not. If not, do nothing. If so, process the message. Then, you can install all the event handlers once when the socket is first connected.
You can even abstract that:
socket.onWhenInGame('someGameMessage', function(data) {
// this will only get called when connected in a game
});
And, the implementation of that method would be something like this:
socket.onWhenInGame = function(msg, fn) {
return socket.on('msg', function(data) {
if (!this.inGame) {
// not in a game, ignore this message
return;
} else {
fn(msg, data);
}
});
};
Then, you just need to maintain the socket.inGame
flag whenever you join a socket to a game or remove a socket from a game. And, you can just program with socket.onWhenInGame()
without worry about all the above issues.
Upvotes: 1
Reputation: 7120
The simplest solution would be to keep state, which lets you know if you're awaiting the second event or not.
let waiting = false;
socket.on('listentome', () => waiting = true);
socket.on('actualmessage', () => {
if (!waiting) return; // if we're not waiting for this event, ignore it
waiting = false;
...
});
Upvotes: 2