Reputation: 43
I have a server which is built in a thread-per-client way. Lately, I bumped into a problem that I'm having a really hard time to come up with a solution to, so I thought to ask some help.
My server hosts a lobby, in the lobby there are many rooms (all of them owned by users), and in rooms there are players. Each room has an admin, when the admin chooses to leave - the room is closed, and all of the users are supposed to return to the lobby.
Now, I already have a working code - but there's the problem, I have no idea how should I have the other clients also exiting from the room. The code running in the thread is as following:
while(in_lobby)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}
while(in_room)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}
while(in_game)
{
//When a game started
//Not practical right now, though
}
There is no problem messing with the booleans from a thread of one client to another (because they are not exactly local variables, they are several conditions that I could change through the thread which is handling the administrator choosing to close the room).
The problem occurs when the condition DOES change, and the while loop is supposed to exit before the next iteration. Why does it occur? Because in the beginning of the iteration there's a recv()
call, which awaits the client's messages.
Now, the condition is okay, everything is okay, but the loop won't continue (so it won't reach the next iteration - to see that the condition is false) until the server receives a certain message from the client (which it shouldn't, because closing the room doesn't depend on a regular user - the user only receives and alert, which is also sent through the admin's thread, about the room being closed).
My question therefore is:
How can I perform what I want? How can I make these users to break out of the loop, returning to the lobby (there is no problem with doing so with the admin, as his thread is the one that does everything and he returns to the lobby successfully) without changing the whole architecture from a thread-per-client way?
Upvotes: 4
Views: 2216
Reputation: 1263
The Stroika thread library - https://github.com/SophistSolutions/Stroika/blob/v2.1-Release/Library/Sources/Stroika/Foundation/Execution/Thread.h - supports interruption through cancelation points. This solves about 1/2 of the problem.
But what solves the rest - for networking applications - is that the networking library provide wrappers on networking objects like sockets (https://github.com/SophistSolutions/Stroika/blob/v2.1-Release/Library/Sources/Stroika/Foundation/IO/Network/Socket.h) - which automatically transform blocking operations into cancelation points. This allows you to write your networking applications simply, and just tell a thread to cancel/abort, and any ongoing networking calls are canceled automatically (by the library).
Upvotes: 0
Reputation: 182753
Refactor your code so that there's one and only one place where you block on recv
. Then you can move the client around all you want without having to disrupt the thread blocked in recv
. You still want to receive a message if the client sends one, right?
So when a room is closed, the thread that closes the room can move clients out of it with no need to disturb the threads waiting for messages from those clients.
Upvotes: 0
Reputation:
Because in the beginning of the iteration there's a recv() call, which awaits the client's messages.
I'm not familiar with the particulars if what you're using, but two tricks that can be used in similar circumstances are:
recv()
reads from.With the intent that either of these will force recv()
to return.
If you can't do this with winsock, then another solution is to add another layer. You write a wrapper class that manages the complexities of how to read from a socket while still being able to receive notifications from other sources. Then your clients only use the wrapper class for receiving communications.
In the long term, this solution is probably better than using recv()
directly anyways, since it separates concerns; client code is only worried about how to deal with the client and not the details of how to do robust communications, and the communications code only has to deal with how to receive and relay communications.
Upvotes: 1
Reputation: 1
"Because in the beginning of the iteration there's a recv() call, which awaits the client's messages."
Probably you don't want to have a blocking recv()
call at this point in 1st place.
There's the select()
function from the Windows Socket API, you can use to observe the state changes for a number of socket file descriptors.
Basically you will have a thread running a loop, and poll the return value of a select()
call. Not to hog the CPU with a tight loop, a reasonable timeout value should be specified, but it's possible with a zero timeout, and just poll the available states as well.
If the return value signals that there's some action available for one of the observed sockets (select() > 0
), you can inspect the observed socket file descriptors if they were triggered using the
FD_ISSET(s, *set)
macro.
Upvotes: 1
Reputation: 14360
Besides πάντα ῥεῖ's comment, you could try to implement a Provider-Consumer pattern.
Use a queue for store clients messages and read from it using that loop, if no messages to read just continue. This way the loop don't waits for the message arrives, that is the "Provider's" Job (the loop is the consumer since is consuming messages from the queue).
So, move the code calling recv
out of the loop and use it for feed the queue.
Queue queue; // This has to be thread save.
class Provider: Thread
{
void run(queue)
{
while(1)
{
message = recv(); // This is where waiting occurs.
queue.push(message);
}
}
}
// This looks like it fits inside another thread. ;)
while (some_condition)
{
message = queue.pop(); // This returns immediately.
if (message)
{
//... so some things.
}
}
Upvotes: 0