A Specialized approach to Client Server [Documents/03.1-Specialize.txt] Ken McGlothlen, 30 Sep 93 | The first are two are primarily messages responded to, while the last is | primarily messages sent. | | - access to the players | - information on the "geography" of the "world" | - "clocking" of "events" This isn't a bad breakdown. Let's examine it somewhat: Poker: 1. Players: Dealer (deck) and players (hands, wallets). 2. World: Table. Geography: Items on it (cards, kitty). 3. Clocking of events: Take turns. Squad Leader: 1. Players: Russians (units) and Germans (units). 2. World: Hexmap. Geography: Terrain-based. 3. Clocking of events: Take turns; turns are multistage. Cyberpunk role-playing game: 1. Players: Whoever (individual stats and inventory). 2. World: Freeform. Geography: Varies. 3. Clocking of events: Simultaneous. Note that in each case, the server must define each one to begin with, but that information in the player's bailiwick can be easily handled by the clients. The server has the option of taking responsibility for the world---but in general, this isn't such a good idea, because you want as much of the CPU load as possible to be done by the clients rather than the server. So the server must instead assume the responsibility of informing all the clients if there are any changes to the world. (Russian client to server: "Moving SMG-24 at 2-AE to 2-AF." Server to all clients: "Russian moved SMG-24 at 2-AE to 2-AF---update your maps accordingly.") The server definitely wants to handle event-timing, however. Clients can easily get out of sync depending on local load, network timeouts, and the like. | Obviously, each [client] needs to be able to get ahold of the others. It is | [...] possible to just have a list on the server, and clients can add or | subtract themselves from the list. It should also be easy for the client to | get a (current) copy of the list from the server, in order to send messages | to other clients. By having players directly message each other, we can | greatly simplify the work of the server. Maybe. Of course, this means that each client will have a connection to the server and all the other clients, which wouldn't be so bad, except that then you not only have to keep a current list on the server, but then inform all the clients that someone dropped out, so they can close their connections. In general, it's easier to leave interclient communication with the server. It simplifies things quite a bit. (Of course, I'm not too familiar with NeXT's Distributed Objects yet, so it may be a lot less painful nowadays, but. . . .) | [Line of sight example.] To do that, I would (possibly, depending on how we | work it) message the server asking whether the "geography" would allow it. Not necessarily. If the clients are informed of the geography at startup, they can calculate line-of-sight and such themselves. This reduces the work that the server has to do. | I think these may be two separate problems (topology and terrain) - Agreed. | and hence two separate protocols, but I am not sure. What kind of | information do clients want about them? Is it possible to parametrize most | of it? Or will clients use "isKindOf" messages to figure out what type of | terrain they are dealing with? I don't think you can reduce topology and terrain to such general terms. The *World objects (SquadLeaderWorld, SFBWorld, what have you) are all going to have to be written differently, I'm afraid. You could probably parameterize 2D discrete maps, for example (square grid, hex map, path-connected areas, or what have you), but you probably couldn't generalize it to 2D continuous spaces. And I'm not sure you'd even want to. | Closely related to topology is the issue of location. Who knows where a | piece is located: the client, or the server? The former has the advantage of | encapsulation, the latter has the advantage of efficiency. For example, | consider the case where you need to find all the pieces from all the | different players within a given region. Is it enough to poll all the | players for their pieces? Or would it be much better for the server to have | all the pieces organized by location, and clients poll the SERVER to find | 'nearby' pieces, rather than polling all the other clients. There are several approaches here: * Smart server, dumb narrow clients: Server knows where everything is. Clients have to ask the server for everything. Clients never communicate directly. Advantages: Clients run fast, and can be simply programmed. Disadvantages: Server is monolithic and slow. * Smart server, dumb broad clients: Server knows where everything is. Clients have to ask the server for everything. Clients can communicate with each other. Advantages: Clients are almost as fast, and players can send messages back and forth without involving the server. Disadvantages: Clients become quite complex, and server doesn't get that much faster. * Smart server, smart narrow clients: Server knows where everything is. Clients are informed of as much as possible when they connect to the server. Clients never communicate directly. Advantages: CPU load is partially removed from the server---clients no longer have to query the server each time it wants to display a map, because the server just informs the clients of changes. Still fairly simple. Disadvantages: Clients have to be smarter, takes more time to program. Vulnerable to dishonest client programs. * Smart server, smart broad clients: Same as above, except clients can talk directly to each other. Advantages: Server has even less of a load. Disadvanages: Clients are even more complex, but otherwise the same. * Dumb server, smart narrow clients: Server only handles interclient communication. Clients each manage their own copy of the world. Advantages: Server becomes very easy to program---it's just a message relayer---and it's fast. Disadvantages: Highly vulnerable to dishonest client programs, since there is no ability to check for a client that lies. * Dumb server, smart broad clients: Server acts only to connect players to the game; the onus of the game is laid squarely on the clients. Advantages: Server is about 80 lines of C code. Disadvantages: Clients are very complex, game is vulnerable to network timeouts, dishonest clients are absolutely fatal to the game. Unfortunately again, there isn't really a nice general approach that you'd use. For example, for Squad Leader, I'd have a smart server and smart narrow clients. The server would inform each client of the board at game-start, and then any subsequent movements that that player would be able to see. There would be little reason for interclient communication---the players aren't going to be talking *that* much, and the server isn't going to be terribly busy *all* the time. LOS calculations and move-legality can then be done by clients, and when a legal action is made, the client can then inform the server, which will make the decision on whether to inform the other client that something took place. And so on. The server decides who wins the game, and informs the clients accordingly. On the other hand, take an interactive 2D continuous-map game---like netrek. Smart server, dumb narrow clients. Server knows where everything is, and handles all communication. The clients concentrate solely on displaying whatever the server says, and receiving input and shipping it off to the server in condensed form. | [Random thought: make the location information a C++ class for encapsulation | and efficiency] Got an example of what you mean by that? | This breakdown implicitly assumes that the only "spatial" interaction pieces | have is moving, sensing, and attacking. Is that too simplistic? Yes. | Can other activities - for example, in role player games - be handled in | those terms, OR be handled player-to-player independent of terrain? Nope. If only. There's acquisition, repair, communication, and all sorts of secondary concerns (encumberance after acquisition, for example) that don't generalize very well. | Is there a representational model that works well for both 'digital' and | 'visual' information? Possibly for some cases. Not all, by any means. Of course, the question is a bit more vague than I like. | Virtually every wargame has some system which determines who can act at any | given time. The simplest is where each player is given exclusive turns in | which they can act, and other players just react psuedo-passively. In SQPR, | though, there are individual leaders on the board who go in a precise | sequence - somewhat independent of which side those leaders are on. Would | those leaders be separate players? Pardon? By "players," do you mean "clients"? Or what? | Consider further that some pieces may have a time-varying state, which needs | to be updated regardless of whether the player does anything with them. Does | the server send the player a 'clock tick' for the smallest possible time | increment? Or, rather than a clock, does the server maintain a general | "event queue", and it sends messages out when they are needed. And if so, | what messages can it send? Again, I think you're trying to generalize too much. I think the best we're really going to be able to do here is to come up with some *typical* tools---such as a general hexmap object, or general server object, for instance---which people can then subclass into their own games. For example, you might have a Hex class which has something like the following: hex = [Hex init]; // Creates a new hex. [hex setTerrain:HEX_FOREST]; // Makes it a forest hex. oceanhex = [[Hex init] setTerrain:HEX_OCEAN]; or maybe even [hex setTerrain:HEX_GRASSLAND]; [hex placeTown:2000]; // Hex contains a town with a // population of 2000. [hex riverEntry:NW exit:SW]; // River enters hex from NW side, and // exits on the SW side, passing // through center of hex. And then you'd want a Hexmap class: map = [Hexmap init:20 :30]; // Allocate a 20x30 hexmap. hex = [map hexAt:10 :10]; // Return the id of the hex at (10,10) perhaps. But I think that's about as general as you're going to be able to get on mapping, unless you have something insanely cool in mind. | Then there is the issue of real-time play (or what I call scaled-time - is | there a better word). I like real-time, myself. | How, for example, do we notify Diplomacy players how much time for discussion | they are allowed? If we have smart pieces or computer players, how can we | notify them to "do their thing"? 1. Server sends to all clients: "Start diplomacy, you have 15 minutes." 2. Server starts countdown timer; clients each start their own countdown timers. 3. If a player wishes to engage another player in diplomacy, they send a request to the server, which informs the other client. If that player accepts, the server connects the two. (Yes, the server handles all the interclient communication. In this case, the load on the server is *not* great. Lots of waiting for user input, and a maximum of seven players? The load is almost laughably low, even if they were all talking at the same time to everyone else.) 4. If a player no longer wishes to initiate diplomacy, they click on a switch, which sends an "I'm done" message to the server. 5. If a player subsequently wishes to engage in further diplomacy, or is engaged in diplomacy by someone else, the "I'm done" message for that player is cleared. 6. If all players have their "I'm done" flags set, the server is aware of this and ends the diplomacy period prematurely by telling each of the clients. 7. Otherwise, when the server's clock runs out, it informs all the clients that diplomacy is over. Note that in this case, the clients' countdown is just to inform their own player of how much time is left---only the server's countdown is definitive. | 1. the servers will have to trust the clients; if information is hidden from | the (human) player, the client code must take care of that. Otherwise, the | restrictions make the server too game dependent. While I see your point, this may be too vulnerable to dishonest clients. Servers can usually be made to take care of this sort of thing without too much load, if designed properly. Of course, this means that you won't have a generic server program, but on the other hand, you should have enough objects that putting together a game-oriented server shouldn't be too hard. | 2. I would like to have only one server protocol, with two or three server | objects that can handle virtually any situation. All the complexity gets | shifted onto the clients. Is that a reasonable goal? Again, I think this may be unnecessarily complex, and in fact, may not be as reliable as you'd like for a multiplayer game, as far as race conditions and dropped connections go. | 3. Is the issue of multiple participants sufficiently general that we should | isolate the server part from the rest of WarKit? Certainly, you should have a message-passing object in the WarKit, which passes arbitrary data (like a TCP/IP socket, only easily managed). | Do we care if people might want to use this for things like, I don't know, | "GreyBoard"? Why not? | 4. The stereotypical client-server problem is that of a database (a file | server just being a special case of a database, of course:). Is that a | useful paradigm for thinking about the WarServer? Yes. I'd like to see slightly more of the load on the clients, though, than your typical database server. :) | Well, I hope you enjoyed wading through this. Please correct me if I made | any obvious blunders. Also let me know whether you think I am approaching | the problem from the wrong (or right!) perspective. Now, let the discussion | begin! Just be careful that you aren't trying to go for something too perfect. The joy of object-oriented programming is that you have a bunch of building-blocks which you can then slap into a program in short order, rather than having some hypergeneric do-it-all object that does EVERYTHING. For example, I see no problem with having a SquadLeader-oriented server that handles almost everything, with clients that just do display, user input, and line-of-sight and movement checking. This can be done easily enough by subclassing a simple server object and adding methods for movement, player messages, and the like. For example, in the client: char s[80]; - unitFire: (id) unit at: (id) target { // LOS checking goes here; if successful . . . sprintf( s, "FIRE %s %s", [unit name], [target name] ); [self sendToServer:s]; } - sendToPlayer: (id) player msg: (char *) s { sprintf( s, "MSG: %s", s ); [self sendToServer:s]; } and so on. That's a little simplistic, but at least you don't have to worry about big-endian vs. little-endian. :) ---Ken