[java] Client/server command execution design decisions..

Started by
9 comments, last by Son of Cain 18 years, 10 months ago
Say you have a "command" object on a client, and it needs to communicate with a server. Maybe it needs to query a database the server is in contact with or effect the server in some other way. There's also other clients which are sending the same types of commands simultaneously (as a game would have to do), so you have to make sure those commands are executed in a way that they don't "run into" each other. The client commands are strategy wargame type commands--usually single execution (or at least not repeated often), varying greatly in type, and not executed often--I'm not talking a fast paced shooter or anything here. (What I'm actually working on, I guess you could call a framework. And no I'm not using applets.) Has anyone ever sent an object over a network connection via ObjectOutputStream/ObjectInputStreams? Is this easy or time consuming to implement? (also using NIO networking) Or perhaps what I need to do could be accomplished using RMI? I can think of three possible ways to accomplish this... 1) Create command objects which essentially know how to send their data over the network and rebuild themselves/get rebuilt when they arrive on the server/client. I have a working prototype of this method. (I am wondering if it is the best method.) This does have issues. Ex. for every type of command there is, there has to be TWO different (but similarly named) classes--one on the server and one on the client. The only major differences between the two are what they do, and not much else. It's not hard to add new commands or alter stuff, but I'm wondering if this is an optimal solution. 2) Use ObjectInput and ObjectOutput streams to somehow send objects over the network so they are free to go back and forth as needed, doing whatever they need to do to finish executing. I'm not familiar with how this would work or if it's even feasible. Is it worth looking into? 3) Use RMI -- ex. have a client command object invoke a command on the server which tells the server to send a chat message to all clients.. or ex. have a client command execute a database query on the server or ex. have a client command execute a login request on the server ...and so on. I'm not sure how well this method would handle multiple clients all doing this stuff at the same time. Nor am I familiar enough with RMI to know if I'd need to worry about commands being executed "in part" while other commands begin executing in parallel (this could be very bad). IOW this would have to function as if it were all running as a single thread on the server. This may be exactly how RMI works, but I don't know. I've also read that RMI has more overhead than the first two methods probably do, although that probably isn't a factor in my case. It's more...short development time, superior design for understandability, and the ability to easily add new types of commands that I'm really focusing on here. But is RMI worth looking into for this purpose? Other suggestions are welcome too, of course.
Advertisement
You shouldn't need to worry about overhead.

If your command objects are small, they will be serialised quickly and into a compact stream. For example, you could store:

- Ints, shorts, longs, chars, floats, bytes, booleans etc

Pretty cheaply.

If you have something like a group command (say "Send these 100 troops over there"), you'd have to put in an array.

So it would be an array of ints with the IDs of those units the command applies to. Arrays of primitive types are nice and compact too.

Avoid having objects in objects in objects - it'd work, but it makes serialisation slower (which probably doesn't matter) and the command messages bigger (which probably does).

Use polymorphism for your command objects if you like - that would be quite neat.

As far as multiple clients are concerned, the server should probably be single-threaded - or have a single command despatch thread.

The server then decides what order to process the commands in, and ensures that every client also follows the same order.

Clients should process commands when they are sent back from the server, even if they originated locally. This ensures that all clients receive every command on the exact same frame number as the server (even if they are slightly lagged), so they have the exact same effect.

Then ensure that any random number generators are seeded appropriately (so they're the same on every client), and you'll have a game which plays correctly.

Mark


And have the commands on the server be validated and have contingencies.
Because of lag across the connection and processing delays on both sides the
game state may have changed since the time the commands were created.

ex- An 'attack object X' order will have to handle the case that object X may not exist any more (or worse its slot may have also been reassigned to a new object -- requiring a unique ID system (and that data sent with the order....))

Another case would be other objects moved and now block the move order sent by the client (which had seen the situation as not blocked at that time...)
The server wouldn't need to validate commands though, because they would be validated by the clients anyway. The server would execute the commands locally (and they'd have validation there), but maybe not until after it had sent them to the clients.

If a command saying "attack a nonexistent unit" arrived at a client, it would of course, be ignored. So it wouldn't really matter, as this wouldn't happen very often.

And yes, it would be good to have unique IDs for things in the game. Or at least, one which didn't get reused very often.

Things that aren't the target of any command however, don't need IDs at all, all clients will just have identical copies of them due to the deterministic behaviour of the game. So stuff like bullets and bombs, would exist identically on all clients without needing an ID.

---
With this scheme, there would be some methods of cheating, but not by doing impossible things, only by *knowing* impossible things. For example, the areas of the map revealed, would be stored only locally and not validated at all, probably.

Mark
Quote:Original post by Tebriel
Has anyone ever sent an object over a network connection via ObjectOutputStream/ObjectInputStreams? Is this easy or time consuming to implement? (also using NIO networking)


It's very very easy. However, it's not really what you want to do at all. I strongly suggest you brush up on your OOP, and the definition of an "object" (object == data + methods that operate on that data) - and you will see that you actually are only sending data ("the name of the command the player wanted executed")

The server should damn well already have whatever methods it needs to operate on said data! ;).

Sending an object is many times worse than just having an object in memory: an object requires as much as 6 times as much space as its data alone (i.e. 6 times as much b/w), but *sending* an object means you ahve to send all the code too, which adds probably 100 times as much overhead.

So just send raw data. Anything else is wasteful in the extreme, and will make your game noticeably slower (even for a turn-based game).

Quote:
Or perhaps what I need to do could be accomplished using RMI?


If you really really want to send objects, i.e you are allowing your clients to re-write the rules of the game whilst they play (!) then RMI would be the easiest way for you to make this work: it handles lots of complex problems that come up when you are sending objects around, and otherwise you'd have to implement these on top of OOS/OIS yourself.

Quote:
I can think of three possible ways to accomplish this...

1) Create command objects which essentially know how to send their data over the network and rebuild


This is the bog-standard approach, so long as you ONLY send their data. This is NOT sending objects, it's "using objects to send some data", i.e. the "command objects" are not "sent" they are merely *asked* to send a *message* using some proprietary scheme inside themselves.

Quote:
Nor am I familiar enough with RMI to know if I'd need to worry about commands being executed "in part" while other commands begin executing in parallel (this could be very bad). IOW this would have to function as if it were all running as a single thread on the server. This may be exactly how RMI works, but I don't know.


It's good to worry about such things, but don't. Instead, just write your server code as you would write any multi-threaded code: if you don't knwo how to write safe multi-threaded code, you're screwed anyway, so you'll need to learn that. If you do, there's nothing special or strange that you need to know in addition (although, having been in your position, I know how you feel - but honestly it's not as complex as you fear it might be)
I'm familiar with OOP and I know what an object is. My poor communication of this "sending an object" concept is due to the fact that I have no idea what these "object streams" are. The concept seems somewhat odd to me. It just sounded like it might be an alternative...but it's probably not a good one.

Currently I *am* only sending data of course--the data is used to build similar command objects (but with differing methods and possibly some differing data) on a client or server. I'm also already using polymorphism.

From the sound of things it seems like my current technique holds up pretty well against the other two choices. Some of these command objects will get more complicated than simply "move troops to point x,y".

The main thing that concerns me about my current technique is the number of "dual classes" I have (server and client versions used for each command), but it might not be such a problem.

Ack..unexpectedly out of time here, will have to clarify more later today. Maybe I'll post a UML diagram later or something.
My two cents about it (not original thinking, it's a well known solution for the matter at hand):

Develop a framework (for NIO, or standard IO) composed of an Event interface, and basic Event implementations. The Event represents a command, something of interest for your game.

This Framework will also have one utility class to "mount" / "unmount" an Event. For NIO, you should implement the proper order of adding data to the buffer, and the proper order to take it out and read.

Last, develop a queue of Events, to be used by your client/server implementations. Also, you will need a Thread pool to work on the received data, and to send data out as well.

You shall have a bit more of work, but you will be left with a framework, used by both your clients and your server, which is by far easier to maintain and tune for performance.

You can also define special classes to deal with specific in-game Events, so that you can treat them "in a different manner". Say you have that nasty movement interpolation, that insist to be laggy? Define a specific controller on the server side to deal with players movement in your world.

All of these concepts are well explained on chapter 6 of Brackeen's book, "Developing Games in Java", here.


a.k.a javabeats at yahoo.ca
I like the mount/unmount concept, I think I might use that to simplify this idea. I already have a sort of queue system so I'm at least part way there. "Event" may be a better term to use, that abstraction perhaps makes more sense than command. Good comments, thanks. Maybe I should check out that book. :)
The nice thing with Events is that you can easily understand the concept of firing an event, and notifying those interested in it. Add to each NIO Event an array of interested clients IDs, and let the server broadcast your event to them when it receives your event for processing.

It helps a LOT since you can concentrate in a single point on your code, and improve functionality to the game as a whole. Going for a framework like this also lets you reuse the code in any project that requires multiplayer functionality, since almost any multiplayer game relies on events.

Son Of Cain
a.k.a javabeats at yahoo.ca
redmilamber: I think you've misunderstood.

Sending an "object" in Java, of course only sends the data members of the object, not the class itself, which has to exist independently at both ends otherwise it won't work.

Secondly, these objects we're talking about sending are not "Game" objects which are modelling real things (tanks, troops etc). They are "Command" objects which just contain fields pertinent to a player's command.

So they're actually quite suitable to copy across the network.

As far as overhead is concerned - I don't know, but I'm pretty sure it isn't "6 times more" than the size of the data. You just invented that figure out of thin air.

Java uses binary serialisation which only throws the members of the object across the network. It doesn't send anything not really necessary (except of course, some header info to tell the VM at the other end what class it is etc).

So yes, there is *SOME* overhead, I doubt very much whether it is "6 times more".

Mark

This topic is closed to new replies.

Advertisement