thoughts about client/server split (where to go from here).

Started by
10 comments, last by cr88192 11 years ago

basically, as-is, my engine is somewhat strongly divided between the client and server.


the client is generally responsible for rendering and user/input.

the server basically runs all the game logic (as well as the world-loading: the world contents are streamed to the client).

for example, player movement physics, ... are run on the server, as well as it managing all the animations, events, ...

so, the player tells the server "I want to move in this direction, and the jump and fire keys are held", then the server does the physics, and hands this back to the client.

basically, the client and server (even in a single player game) largely don't share data directly, but communicate via passing messages.

in my engine, these are lists / "S-Expressions" (sort-of: in engine, the lists are passed directly, and over the network, a compressed binary serialization is used for the lists, rather than the using textual S-Expression syntax).

but, then I was thinking some of the matter of the possibility of a server supporting a potentially larger number of clients:

in this case, it makes a lot more sense for the servers to do a lot less work, and offload more work to the clients (the server then wouldn't so much micro-manage everything, but would instead more serve to facilitate communication between clients).

for example, clients could manage many things on their own:

movement physics; animations frames; ...

then they would tell the server: "hey, I am at this location, currently in this position, ...".

the server then doesn't have to do much with the player besides tell all the other players about their current position.

and, also, this largely eliminates the issue of poor ping-times hindering movement.

possibly, the client could partly also take over responsibility for things like taking damage from the environment (stepping in lava, ...), or possibly even handling non-projectile weapon fire (client fires gun, and then tells the server about any damage inflicted, ...).

however, at the same time, a person doesn't really want to have all this hard-coded in the client, since this could hinder some possibilities from existing in the game logic (say, for example, drivable vehicles are added, but then existing clients may have out-of-date logic, making the client and server versions more of an issue).

luckily, in my case there is a likely solution:

server sends script code to the client, and then client runs this logic as script code.

in this case, the hard-coded parts of the client can remain general, and possibly any client-side game logic can then be treated like resource data.

unclear if better here is using files, or just stuffing code directly via messages (both have possible merits).

IOW: (stuffcode "... big gob of script code ...")

then the glob of code is compiled and executed.

another idle thought is if clients should persistently cache parts of the world they have already seen, such that the server then doesn't have to re-send them every time the same client reconnects to the same server.

a guess here is adding a time-stamp to each voxel chunk, such that the client can tell the server which version of the terrain it currently has, and the server can then re-send any chunks which have changed since then (updating the client to the newest time-stamp), and save having to resend all the nearby terrain every time.

also considering other possible tricks as well, like allowing the game to mostly skip fully decompressing "non-interesting" chunks, ... (say, a chunk which is just solid rock or similar and otherwise completely hidden/blocked inside the terrain, ...).

thoughts?...

Advertisement

Some thoughts:

  • Comparing with the Model-View-Controller, the server would be the Model.
  • The client can handle much of the animations. The server just sends a "jump" message, and the client animates accordingly.
  • Don't forget about the problem of cheating. If the client have control over things, they are fairly easy to hack. Even if the average player don't possess the skill to hack a binary, it is enough with one person doing it and giving away the result.
  • If the server controls the player movements, the client will need to use some kind of interpolation or extrapolation to get smooth movements between updates. This may be needed anyway when displaying other players or monsters.
  • To determine if a bullet or arrow hits a target requires effort. Not hard for a few of them, but quickly very hard for MMO.
  • If the client decides who or what is hit, there are time lag problems. The monster may have moved on the server side. If two players shoot at each other, who decides the winner? This can require complicated hand shaking.
  • Using a time stamp on chunks can be good. In my game, I use a checksum, for the same purpose. That way, reversible changes will require no updates. And small single changes can be broadcasted as delta.
[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/

Some thoughts:

  • Comparing with the Model-View-Controller, the server would be the Model.
  • The client can handle much of the animations. The server just sends a "jump" message, and the client animates accordingly.

yep. the client may already need stuff like this to handle latency. say, it calculates the likely animation frame based on elapsed time as part of the LERP process. for client-side animation, it would mostly be a matter of eliminating the use of explicit frame-numbers, and have the client assume that the animation sequence will play in a loop until another animation is chosen.

  • Don't forget about the problem of cheating. If the client have control over things, they are fairly easy to hack. Even if the average player don't possess the skill to hack a binary, it is enough with one person doing it and giving away the result.

noted. the partial idea here though is that if most of the game-logic code is sent via "stuffcode" events, rather than part of the client's binary, it would be potentially harder to hack, but there is always the possibility that the client could rig up something to load their own script-code designed to hack the script-code sent by the server. (sending VM bytecode is also a possible option, but it wouldn't likely be that much harder to hack).

possible cheating though may just be a price that would need to be paid to support larger numbers of connected clients.

ADD: I went and added "stuffcode", but security is still a possible concern, which is part of why it wasn't used previously... basically, the concern would be WRT the possibility of user-run or hacked servers sending malicious script-code fragments to the clients. note that the script VM does include some language-level security features "inspired" by the Unix security model. probably also relevant would be defining a mechanism whereby network messages can be passed to script-code handlers (probably via handler registration).

  • If the server controls the player movements, the client will need to use some kind of interpolation or extrapolation to get smooth movements between updates. This may be needed anyway when displaying other players or monsters.

this part is already done.
currently, server-side player movement is used.

the server does player movement and the client predicts it.

the alternative being that the client could do it, which would mostly save on the cost of all the server-side collision checks.
it does lead to an increased risk of latency-induced interpenetration though (where latency allows multiple entities to be in the same location).

  • To determine if a bullet or arrow hits a target requires effort. Not hard for a few of them, but quickly very hard for MMO.
  • If the client decides who or what is hit, there are time lag problems. The monster may have moved on the server side. If two players shoot at each other, who decides the winner? This can require complicated hand shaking.

yes, this is also why it could potentially be moved to the clients.
each client would shoot, and from their POV, each shoots the other, messages propagate, and both take damage.
this should work ok since probably explicit quickdraw isn't going to be a game mechanic.

this would likely be more of an issue for slow-moving visible projectiles (like rockets or BFG or similar), which would exist as actual entities, and so would likely need to remain on the server (as would probably any monsters or NPCs).

  • Using a time stamp on chunks can be good. In my game, I use a checksum, for the same purpose. That way, reversible changes will require no updates. And small single changes can be broadcasted as delta.

yep. there could be per-chunk timestamps, and per-region timestamps, and regions will be based on the most recent timestamp.

presently, deltas are mostly done via dirty-bits, and the whole world is sent on startup, which isn't necessarily good if long-term client-side caching were used.

a minor question is that of the ideal timestamp format. (real-time absolute, vs real-time relative, vs a version counter).

possible cheating though may just be a price that would need to be paid to support larger numbers of connected clients.

I agree.

The risk of hacking depends on the game objectives. It the PvP is an important part of the game, people will spare no effort trying to hack the client. A thing like a wallhack can give you a big advantage, with no need to change the way the client behaves from the server point of view.

That is, the game can maybe, to some extent, be designed in such a way as to reduce the interest to hack it.

[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/

possible cheating though may just be a price that would need to be paid to support larger numbers of connected clients.

I agree.

The risk of hacking depends on the game objectives. It the PvP is an important part of the game, people will spare no effort trying to hack the client. A thing like a wallhack can give you a big advantage, with no need to change the way the client behaves from the server point of view.

That is, the game can maybe, to some extent, be designed in such a way as to reduce the interest to hack it.

yep.

for PVP, people will hack the game generally to give them an advantage, like things like auto-aim and speed-boosting and similar.

for RPGs, it is generally people trying to hack their gold/inventory/EXP/...

...

I am wondering how much it matters between how much of the code is stored statically on the client (such as program binaries and client-side scripts) vs how much is streamed from the server.

hacking each is likely a little different, since for client-side binaries and scripts, they mostly have to hack the code they already have, and for streamed contents they basically have to hack the behavior of the code that comes "over the wire" (such as by patching).

one example of a patch-hack strategy would be them hacking over some existing code used by the server-sent script, and then when this script executes, the code proceeds to patch-over the relevant functions/methods in the server-supplied code.

one possibility, at the language level, would basically be to mark classes as non-patchable. however, likely people would just modify the VM to make this feature no-op.

Back to the main topic (that I evaded).

server sends script code to the client, and then client runs this logic as script code.

This sounds very interesting! It may require a lot of effort and careful thought, but the potential is big. As you say, it will allow for a big flexibility.

Apart from that, there is a kind of a problem that has to be investigated. In principle, you will download executable code (scripts) to users of the client, and run it. It sounds like opening a door for viruses, depending on how well the system is defined. If the script doesn't allow for any input or output outside of the game logic, then it should be safe. But stating something is safe may not be enough to convince users...

A follow up idea: The client need not be much more than a game engine. All the rest is loaded dynamically.

Taking this idea even further, what we almost have now, is almost something like a html5 game, using webgl?

[size=2]Current project: Ephenation.
[size=2]Sharing OpenGL experiences: http://ephenationopengl.blogspot.com/
The first thing you need to assume is every message to and from the server can, and will be looked at by the wrong people. Packet sniffers are a dime a dozen. While it will be nearly inpossible to stop chams, and other visual hacks. Writing small pieces of logic into your server to check for hacking, and then flag an account for potential banning later is your best option. You can also have players keep track of each other. If 6 players say player Y has 0 rounds left, and player Y keeps sending shoot commands without a reload. Naughty naughty. That being said - you would need to use a public private key pair on your stuff code if you want it to be insanely flexiable and only allow your game to run code sent by your server. Or something of the sort or as said before you won't have a hacker issue - your game suddently just became a massive botnet.

Back to the main topic (that I evaded).

server sends script code to the client, and then client runs this logic as script code.

This sounds very interesting! It may require a lot of effort and careful thought, but the potential is big. As you say, it will allow for a big flexibility.

Apart from that, there is a kind of a problem that has to be investigated. In principle, you will download executable code (scripts) to users of the client, and run it. It sounds like opening a door for viruses, depending on how well the system is defined. If the script doesn't allow for any input or output outside of the game logic, then it should be safe. But stating something is safe may not be enough to convince users...

A follow up idea: The client need not be much more than a game engine. All the rest is loaded dynamically.

Taking this idea even further, what we almost have now, is almost something like a html5 game, using webgl?

security is a worry, and I don't necessarily want to keep everything locked down either.

FWIW, as-is, scripts internally use a Unix-like security model, where pretty much every (normal) object in-language has a UID/GID pair (UGID) and a set of access rights (Read/Write/Execute/Special).

theoretically, at least, all accesses are checked, to validate that the code has the correct access rights for a given object or method or array or whatever.

this can be compared sort of to the notion of ring-based access protection in x86 (separating kernel memory from application memory, ...).

code then sent from the server can then only call methods or use objects which are set to allow appropriate access (with flags and attributes setting these values).

for now, UID/GID roughly follows lexical-scope style semantics, so stuff is typically remains under the rights under which it was declared.

a difference here is objects, which are owned by whatever UGID was in effect when their respective "new" was invoked (it is also theoretically possible though to be able to create an instance of an object but not have any access to its fields or methods, since these remain under their rights in effect at their point of declaration). whether methods run under their own UGID or the callers' is controlled via a flag.

currently, code running directly from within the engine typically has "root" access (so can use pretty much anything, at least that the game itself has access to), whereas code from the server currently has "server" access (plans are later that each server will have its own UID), and runs as "untrusted" code, which disables some language features, and some methods may use a "_setugid" modifier (which transfers control to the UGID under which the class was declared, vs using the caller's UGID).

stuff which doesn't have an obvious owner (such as stuff created by C code, ...), defaults to belonging to "root" and has "root only" access.

also, while run-time checks aren't free, I am generally trying to design things such that static access-rights checks can be used in most cases (similar to type-checks).

theoretically (assuming everything is sufficiently nailed down) this should hopefully prevent malicious server-supplied code from compromising the client.

however, given normal engine script code currently runs as "root", this doesn't really protect against user code hacking the server code.

a partial solution here would be running most normal client script code itself under an untrusted user, meaning that while it can export APIs (to server script code), it can't actually do anything to server code, but a determined user can probably hack around this.

if code is running as "root" pretty much all bets are off though.

I hadn't supported server-supplied scripts previously though mostly because a lot of this stuff is "unproven" (as-in, I have little idea how much is actually secure, and the sort of piecemeal way it has been done thus far leaves room for uncertainty).

while I have taken some ideas from browsers (and from things like Apache, ...), it isn't exactly like HTML5/WebGL, in that the client-level interface is generally a bit higher level, and the language design is generally vaguely closer to AS3...

it isn't really intended to be for something like a traditional MMO (where a single party controls the entire gameplay and content experience), but more where people can run their own servers and supply their own content, so more like traditional multiplayer in this regard.

partly this is as-is, I don't have the money or the resources for the level of server beefiness to run something like an MMO anyways (just a single lone developer hoping for "some" sort of income...).

possible cheating though may just be a price that would need to be paid to support larger numbers of connected clients.

I agree.

The risk of hacking depends on the game objectives. It the PvP is an important part of the game, people will spare no effort trying to hack the client. A thing like a wallhack can give you a big advantage, with no need to change the way the client behaves from the server point of view.

That is, the game can maybe, to some extent, be designed in such a way as to reduce the interest to hack it.

yep.

for PVP, people will hack the game generally to give them an advantage, like things like auto-aim and speed-boosting and similar.

for RPGs, it is generally people trying to hack their gold/inventory/EXP/...

...

I am wondering how much it matters between how much of the code is stored statically on the client (such as program binaries and client-side scripts) vs how much is streamed from the server.

This really depends on your goals and what you move over to the client. On an mmo I worked on recently the money issues were not "hacks" but instead exploits. What I mean is that the players were unable to manipulate their money since the server was 100% inventory/item/coin authoritative over transactions in this area. The exploits were mostly goofs in script such as not marking a quest complete and non-repeatable such that the player could get to a stage of a quest, get the reward, recycle it for components, get another reward repeat. Overall, this worked out pretty well with the exception of the goofs which are pretty much inevitable in any game.

From the security perspective though, the scripts worked quite well for the most part. The client side scripts simply maintained a container of what the server told the client it had. Any modification was done via api calls which went to the server, even moving an item from one inventory slot to another was transmitted to the server and not performed until the server said it was ok. Given this, players could change the display scripts but the server ignored such information and always used its authoritative information. Also of note, the bandwidth cost is trivial since even if the player moves a single item back and forth 50 times a second it is still 100's of times less bandwidth than movement.

hacking each is likely a little different, since for client-side binaries and scripts, they mostly have to hack the code they already have, and for streamed contents they basically have to hack the behavior of the code that comes "over the wire" (such as by patching).

one example of a patch-hack strategy would be them hacking over some existing code used by the server-sent script, and then when this script executes, the code proceeds to patch-over the relevant functions/methods in the server-supplied code.

one possibility, at the language level, would basically be to mark classes as non-patchable. however, likely people would just modify the VM to make this feature no-op.

Worrying about patched code is generally not a worthy use of time. Once the client and data files are on a users machine they can/will modify them in massive ways and there is not much you can do about it. Even if you go hard core with something like Blizzards Warden system it is generally easy enough to bypass such that wasting your time on it is just a game of whack a-mole. Take that with a grain of salt though, there are some simple things to be done on the client which raise the bar to hacking without getting caught and those types of things I have done and suggest doing, just don't waste too much time on them as they will eventually be hacked. I personally suggest a basic minimum such that after every game patch it will take a couple days for hacks to update thus putting the burden of the whack-a-mole game on the hackers and not on the programmers.

Anyway, as to the script itself, we rarely had any problems with that being hacked since there was no benefit to it for the player. We used LLVM to generate the VM and then LLVM jit to run it on the client, but the script was mostly secure by the simple fact that any insecure functionality could not be called by the client. What I mean is basically the same as the inventory/money api code, there was simply no transport method/RPC to call any of the direct manipulation functions and if a client tried to make an invalid call it was immediately drop kicked from the server for an exponential time. (I.e. first attempt to send a bad RPC, 5 seconds before you could log back in, 10 the next time, 20 etc.)

This really only left client side movement, collision and aiming as problems and we caught a huge number of folks using things like CheatEngine pretty simply. I don't remember any bans for actually hacking content/scripts for quite a while and by that time the security team had a running start expecting the hacks to become more sneaky.

Anyway, all said and done. Script is not a major attack vector unless you leave obvious holes in the security. As such, I don't suggest worrying too much about them other than making sure all client/server communication is vetted on the server side before making any important decisions.

the possibility of script-insecurity is mostly because the script-language in my case is largely due to the existence of a root-accessible C FFI.

code running under a non-root user generally lacks direct FFI access, and generally has to go through a "_setugid" wrapper, though I could possibly add support for being able to mark various C functions as callable via untrusted code (vs requiring a wrapper class).

clients hacking servers is likely slightly less of an issue at present, as there is currently no way to directly send code to the server (except possibly via console commands, but these are filtered), and currently no RPC is used (RPC had generally also been seen as a potential security risk). most normal communication is via list-format messages (except partly with voxel data, which are sent mostly as flattened out byte-arrays).

as for gameplay hacking:

the main obvious hacks are mostly, if control if player movement were moved to the client, it would be possible for a hacked client to rig up things like flying/noclip/teleporting/...

currently, my game project doesn't really have any real inventory or persistent player state, mostly because I haven't gotten around to implementing this (this has been on the "planned feature list" for a while though), so these sorts of hacks don't currently apply (but may apply if things like this are added).

I had generally not added them, mostly as for a while I was left debating how the inventory UIs would work and how interaction between the client-side inventory UI and the server would work, and I hadn't really gotten to seriously working on it (it was things like debating if the UI code should run on the client or server, and if in the latter case, how to best handle client/server widget events).

This topic is closed to new replies.

Advertisement