So I've been working on a small game, where the client reports it's position to the server and the server checks the movement of the client so it's somewhat reasonable (to combat speed hacks, etc.), and I've ran into a little bit of a problem.
The problem I'm having is how to quantify the client movements on the server, since the client is just sending it's position to the server, there is a risk of all several packages arriving back-to-back to the server, so that it looks like the client is hacking but in reality isn't. Assuming the client sends three positions, P0, P1 and P2. Something happens on the network on the way from the client to the server so all of these packages arrive at almost the same time. Looking from the servers point of view it now looks like the client is trying to speed hack (say the client traveled 2 units between P0 and P1, which is perfectly valid if they had been spaced as they were supposed to, but due to networking the arrive at the same time).
So the solution I came up with was this: I know the client update rate (100ms), have the client attach it's local time-stamp (not the synchronized clock here, just the local clock) to each outgoing position, then on the server read them and make sure they are spaced by 100ms, but this doesn't work properly either since the client just could modify the time-stamps to be whatever they want.
So my next idea was to use the clock that is synced between the client and the server, the client attaches it's local "server time" clock to each message, and then when this arrives on the server we can check this time against the real "time" on the server, and make sure it's not to much out of sync, and make sure all inbound time-stamps from the client also are spaced by 100ms, but still within the bounds of the master "time" on the server (with some reasonable margin, something like: ServerTime-ClientTime < (RoundTripTimer * 2).
Are there any other solutions to this problem?
Client position and the unreliable nature of networking
One other solution I've come up with is a variation of the last one, since I can verify the game time (this is not the local clock of the client, but the timer that is synced between the client and server) of the client somewhat against the server clock, and since I know that the client update rate for positions are 100 ms apart, I could do something like this, maybe:
This seems (in my head) like it would work
Client: I started moving from Y, I moved forward for 50ms (since the initial movement might not take up the full 100ms) and my position is now X0
Server: OK, I will store your current game time + 50ms as your "movement start time" and X0 as your "initial position"
Client: I moved 100ms forward, my position is now X1
Server: OK, since I can see using my own clock and the initial time that you sent me that at least 100ms has passed, I accept X1 as your new position
Client: I moved 50ms forward and 50ms left, my new position is X2
Server: OK, since I again can see that at least 100ms has passed, X2 is accepted as your new position
Client: I moved 100ms forward, my new position is X3
Server: NO, since only ~100ms has passed since X2 and you said you moved forward for 100ms to X3, but the distance between X2 and X3 is too far for you to have moved there I can see that you are speed hacking.
Client: Disconnected for speed hacking
This seems (in my head) like it would work
Client: I started moving from X, to direction Y at time Z
Server: OK, as your speed is S and time passed from Z is T, your position X0 is now X+Y*T*S
Client: As you say.
Dont accept or deny anything from client, just take the input and update client according to game server rules.
Server: OK, as your speed is S and time passed from Z is T, your position X0 is now X+Y*T*S
Client: As you say.
Dont accept or deny anything from client, just take the input and update client according to game server rules.
Since you know the client update time is 100ms...
I don't know how you're updating the server, but you could increment a counter by 1 every 100ms and make sure a sequence sent from the client is never more than 2 (or something) more than that counter.
Or if your server reacts based on input rather than an update loop you could just keep track of the last time the server received a packet, and the sequence of that packet.
For Example
Client { lastPacketTime, expectedPacketSequence, updateInterval }
on packet receive
Notes:
1. Account for expectedPacketSequence overflow.
2. Maybe let there be a difference of += 2 for expectedPacketSequence just to account for a slight unsynchronization in packet->sequence and the initial value for client->expectedPacketSequence.
3. If they appear to be coming at once, just think of it as the first one (or several) came late and the other one is perhaps on time (it can never be "early", thats what the above code should account for )
I don't know how you're updating the server, but you could increment a counter by 1 every 100ms and make sure a sequence sent from the client is never more than 2 (or something) more than that counter.
Or if your server reacts based on input rather than an update loop you could just keep track of the last time the server received a packet, and the sequence of that packet.
For Example
Client { lastPacketTime, expectedPacketSequence, updateInterval }
on packet receive
elapsedTime = currentTime - client->lastPacketTime;
elapsedFrames = floor( elapsedTime / client->updateInterval );
client->lastPacketTime += ( elapsedFrames * client->updateInterval );
client->expectedPacketSequence += elapsedFrames;
if ( packet->sequence > client->expectedPacketSequence ) {
// sending packets too quickly, no no!
}
Notes:
1. Account for expectedPacketSequence overflow.
2. Maybe let there be a difference of += 2 for expectedPacketSequence just to account for a slight unsynchronization in packet->sequence and the initial value for client->expectedPacketSequence.
3. If they appear to be coming at once, just think of it as the first one (or several) came late and the other one is perhaps on time (it can never be "early", thats what the above code should account for )
So the solution I came up with was this: I know the client update rate (100ms), have the client attach it's local time-stamp (not the synchronized clock here, just the local clock) to each outgoing position, then on the server read them and make sure they are spaced by 100ms, but this doesn't work properly either since the client just could modify the time-stamps to be whatever they want.
Why don't you just number the steps? If you know that the client sends one packet every 100 ms, then number the steps since connection as 1, 2, ... and you know the client time is step number times 100 since connection.
At that point, you know that each new packet should have a higher step number than the previous packet, and you know that the step number should be approximately time-since-connection divided by 100. Exactly how much you allow the client to jitter determines how robust you will be to network problems, but also determines how large a "window" a client has to cheat. There used to be multi-player FPS games where a player would have a switch on an Ethernet hub; turn of the hub, which would cause opponents to stop moving locally, then shoot them all, then turn on the switch again; the queued packets would all go out, and it would look like the player was a run-and-gun ace. Given your numbers, I would accept up to 1 number "ahead" of normal clock, and 3 numbers "behind" normal clock, which allows for a jitter of about 300 milliseconds. (Note: jitter is the deviation of transmission latency from the normal latency, the fixed latency itself does not count here)
You should also consider what happens if the packets are out of whack. You can either just drop the user, which is frustrating for users with bad connections, but deters certain kinds of cheating, or you could snap the client back to some position where you think it "ought" to be, which is frustrating to players in that the game experience under network duress is laggy, but at least they can stay playing.
Client: I started moving from X, to direction Y at time Z
Server: OK, as your speed is S and time passed from Z is T, your position X0 is now X+Y*T*S
Client: As you say.
This only works perfectly if both sides have exactly synchronised clocks (which is impossible to guarantee) and consistent latency (which is also impossible to guarantee), or if you can either trust the client's claim of when 'Z' is (which is impossible to guarantee) or know that the message takes zero time to travel (which is impossible).
That's not to say it might not work well enough for a given game, but this solution looks more elegant than it actually is, unfortunately.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement