Sign in to follow this  
tbuczek

Multiplayer Game Question

Recommended Posts

For your benefit, you can skip everything between the lines following as it is background and what not. I felt the need to include it to give an idea of my limited experience writing games. --------------------------------------------------------------------- --------------------------------------------------------------------- --------------------------------------------------------------------- Okay, first off... I am not a game programmer. I work on enterprise applications for my job, so my knowledge on game development is limited to the books and articles I have read. I have written some small single player games, and even a 3d game (though 90% of the code was stolen... all I did was change the meshes so they were more suitable for my kids). Recently, I started working on a turn based squad combat game. It is a persistant game that is intended to be run in campaigns. If you have ever played Mordheim, or Necromunda, that is about the closest examples I can come to my game. You build a squad of 5 to 10 units, and they persist between sessions. As they go on they grow in experience, gain skills, buy equipment etc... I am sure you all know the drill. The mechanisms to persist the squads is outside the scope of this discussion, that is a much simpler monster. Anyway, there is a bit more backhistory but I will get into it some of the meat Initialy I made the game a peer to peer game, using UDP. I had an action engine, and everything that was done occured in the action engine. Any UI event got associated with an object that subclassed from ActionBase. When an action occured, it would call the actionengine, which would fire the action off to all peers. All that was passed to the peers was the sigiture of the action, and the action arguments. Each peer then got the event and performed the action at the same time. I was all proud of this engine, cause I never really had to worry about multiplay, since all the clients ran the same code, and should have been sync'd, any action on peerX would be replicated perfectly on peerY. Unfortunately, they would not stay sync'd. I ran into issue after issue where this elegant engine just fell flat... Even if I used TCP, I still ran into the problem of random events, and timing. So I went back to the drawing board... literally. Alot of my problems seemed to go away when I re-designed it as a client/server solution. So from a high level I designed what I wanted, then I started getting into the details. I wrote my own Client and Server using TCPListener and Socket class in C#, but it started getting a bit much, so I stole alot code from someone smarter than me (not stole but you know what I mean), then wrapped up his code. Now I have a very nice Client Server application that really doesn't do anything. --------------------------------------------------------------------- --------------------------------------------------------------------- --------------------------------------------------------------------- My question is simply about my design. My server does basically everything regarding the game. It maintains the complete state, and object structure. It resolves every action, and makes every descision. The only thing the client is responsible for is rendering the UI based on the current state as dictated by the server, and of course allowing UI events. (very close to pure MVC, but not completely). The state is maintained cross the different clients by what I called a broadcast event. At a set interval, a client makes a Request to the server, requesting all the new Broadcast events that occured... at which time the server of course responds with all the new ones. In practice this would look something like this: P1 - Client1, P2 - Client2, SVR - Server Attack Sequence P1: UI event: Player selects UNITA, and has him attack UNITB P1: Request Sent to Server. SVR: Recieves Request SVR: Evaluates if the action is allowed. if (Not Allowed) SVR:Respond to P1 with Not Allowed Response Else SVR:Processes Action SVR:Creates Broadcast State Object for both UNITA and UNITB SVR:Responds to P1 with Allowed Response P1:Requests new Broadcast Events P1:Updates State of UNITA P1:Updates State of UNITB P2:Requests new Broadcast Events P2:Updates State of UNITA P2:Updates State of UNITB Like an application server, my game server handles all the business rules if you will... and my clients are just the view. After all that, I am just wondering if this is a suitible design for a multiplayer client/server game? I don't want to get neck deep in this again just to find there was a major design flaw. What are some common design patterns for this type of multiplayer game? (Oh yeah, the game is turn based. I am not brave enough to tackle real time till I can pull this off.)

Share this post


Link to post
Share on other sites
I was trying to ensure the client got every event in order.

Instead of the server sending to all clients, it maintains a list of the all events that occured.. and the clients simply request the latest (any that they don't have.)

I was not sure on this method, cause this means the server maintains a list of every single broadcast event that occured. Also, depending on the interval and the number of clients.. that could be a large number of requests going through the server. Say 1 second interval, with 5 clients is 300 requests a minute (not including normal UI requests).

I ramped up my server to see if it could handle load, and I had 500 clients making requests once a second (from 4 machines on the same network) with no obviously performance issues.. but I wasnt doing binary serialization, just catching the binary, and returning the same thing. Now, I am serializing and deserializng my objects... maybee I should do another load test.

That and.. I spent all this time building a request/response model :)... I didn't see the difference in clients requesting the events or the server sending them out.

Not saying it was the right way to go.. it is kind of a lazy approach... but there is some merit in it. Please illustrate the issues you see in this model.










Share this post


Link to post
Share on other sites
If you're using TCP, and sending all events in order, then they will be received in order on the client. No requesting needed.

Btw: When you say "request," do you mean that the client opens a connection and requests the data, and then closes the connection? That's not efficient -- you'll be much better off keeping connections open. As long as the connection is open, one request per client per second will be no problem up to hundreds of clients.

Share this post


Link to post
Share on other sites
No, a request is just a a Socket Send and Recieve wrapped up into one call. I only connect once... I think :)

I used it to abstract the TCP layer.. but here is some psuedo code.


public Response SendRequest(Request)
{
Socket.Send(Request)
return Socket.Recieve();
}


Should I do this asyncronously?


public void SendRequest(Request)
{
Socket.Send(Request)

}

public void ListeningThread()
{
do (Connected)
{
Response = Socket.Recieve();
FireEvent: OnResponse(Response)
}

}

I just got concenred about the server having to maintain the list of clients to broadcast to, and what events they already processed.

I know I already maintain the clients but only the Socket connection information. What if the server sending the TCP has some sort of exception... then I need to reque the event and resend to that particular client, or am I just not trusting TCP enough cause I haven't used it enough?

In the design I have, client state is made up almost exclusively by Broadcast events. Since each broadcast event is an minor update to the client state, and the current state is assumed, this needs to be 100%. The clients need to get every event. The only object that knows for a fact that it recieved and processed all the broadcast events it was given is they client.

Again, I am taking a lazy approach here. I am not argueing that this is a good design. I think it is a sufficient desgin, but my limited experience means I am making alot of assumptions.

[Edited by - tbuczek on April 10, 2007 11:38:33 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by tbuczek
I know I already maintain the clients, but what about if the server sending the TCP has some sort of exception... then I need to reque the event and resent to that particular client, or am I just not trusting TCP enough cause I don't know enough about it?


TCP is byte-oriented FIFO stream. If anything goes wrong with the data, the connection drops.

That's the whole point of TCP.

TCP handles everything, from checking data integrity, packet re-sequencing, to re-transmits and bandwidth throttling.

And for UDP, you need to re-invent all of those. Albeit you get more control over individual parts, it's not an entirely trivial task.

Share this post


Link to post
Share on other sites
Okay...

so if I get around the issue of clients connecting after the fact (which I can do, by making a bulk state update when the game is started).. a more correct approach would be just send the data, and have the client fire off an event when the tcplistener recieves the data...

Hmmm, I am not positive how I would implement that.

Is there a chance of my broadcast events and my Responses getting crossed?

Say they occur in different threads...
1. Client Sends Request
2a. Server Sends Broadcast event
2b. Server Processes Request
3. Server Sends Response
4a. Client Calls Recieve (Looking for a response)
4b. Broadcast Listener calls Recieve (looking for a Broadcast event)

In the above example... my 2 recieves would be both get invalid data. Or am I just overcomplicating this?

Should I just ditch my request/response model all together and go completely Async?

Share this post


Link to post
Share on other sites
If by "invalid data" you mean "data that the code didn't expect at that time," then yes, you can't control the order of all communication in a multiplayer game.

However, I still think that requesting the data is totally unnecessary. It will just serve to slow down the client, as it would have to wait for the turn-around (which might be half a second) before returning. Possibly OK for a turn-based game, but not for anything with animation on the screen.

I would structure the communication like so:

Client:
Forever
Receive from network if anything is there
Decode and dispatch the data received, if any
Read player input
Send player input to server, if any
Advance game simulation
Render game simulation state

Server:
Forever
For each client
If some data is received, decode it and put it into a global queue
Decode and dispatch the data received, if any
Advance game simulation
For each client
Send updates to client from the global queue
clear global queue

This will work "better" than your current approach, however, it still has problems. For example, what do you do if one client has a network glitch, and ends up stalling the TCP stream enough that the write blocks? You have the option of:

1) block the entire server, waiting on this one client (DoS alert!)
2) use non-blocking writes, and drop the client when you get an error
3) use non-blocking writes, and put the client in a special recovery state when you get an error

I vote for 2).

Also, you want to pace the reading/updating/sending, so you don't do it more often than, say, 20 times per second for a first-person shooter, or 3 times per second for an RPG or RTS (both ways: client->server and server->client).

Share this post


Link to post
Share on other sites
Thanks, that was kind of the assessment I was looking for.

Okay, so I changed the structure a bit. I am now sold on the Broadcast events not being initialized by a request. BUT I can't let go of wanting a syncronous Request/Response mechanism as well... soooo here we go.


I needed to abstract the Network layer a bit.

On the Client:
I have a client listener thread that listens on the network, and adds every incoming event to a local

monitored queue (by monitored, I mean it handles threading sync by using the Monitor.Enter and Exit.)

During my GameLoop update on the Client, I call an Update Client state, which loops through the broadcast

events and handles them One at a time (I was thinking that was better cause it allows the Listener to keep

adding to the queue while I handle the old ones... but a bulk update, where I pull all off the queue might

be better... anyway...)

That gives my Async thread safe reading from the server.. so my broadcast events dont need the round trip.

That leaves me with handling the Request/Response model for when I want a Syncronous call to the server.

Since I cannot guarentee the server wont send a broadcast event inbetween my Request and response
ie.
Client Makes Request
Server Sends Broadcast Event to Client
Server Gets Request
Server Sends Response
Client gets Broadcast Event
Client gets response

I need to wait for my response. This is why I created a local queue to store my network messages. I simply

read from my queue, and any item that is not a response I send off to the handlebroadcast function.. and

keep looking in my queue for my response. Of course this means I need to set a timeout, but either way It

gives me what looks to me to be the best of both worlds.

Below is some Pseudo code.




//Queue with Monitored Enqueue and Dequeue methods for happy fun thread safety
MonitoredQueue Mqueue = new MonitoredQueue


(On Background thread)
ClientListenerThread()
{
int SleepInterval = 25;

do (while)
{
Broadcast obj = ReadSocketAndDeserialize()

Mqueue.MonitoredAdd(obj);
Thread.Sleep(ReadInterval);

}
}

(On Main Thread, called from the Update)
UpdateClientState()
{
object b = Mqueue.MonitoredRead(obj);
while (b != null)
{
HandleBroadcast(b);
b = Mqueue.MonitoredRead(obj);
}
}


(On Main Thread)
HandleBroadcast(BC)
{
//Process Broadcast
}

(On Main Thread)
Response Request(Request req)
{
int Timeout = 30000; // 30 second timeout
SocketSend(req)
Response r = null;
while (r==null || timer > Timeout)
{
Object b = Mqueue.MonitoredRead(obj);

if (b is Response)
r = b as Response;
else
HandleBroadcast(b);


}
}

(On Main Thread)
Response AsyncRequest(AsyncRequest Req)
{
//int Timeout = 30000; // 30 second timeout
SocketSend(req)
return NetCommon.AsyncResponse();
}

[Edited by - tbuczek on April 11, 2007 11:45:39 AM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this