UDP in gaming, what do you do when a datagram is lost?

Started by
4 comments, last by hplus0603 10 years ago

Let's say you want to make a real-time game, perhaps a 2D topdown game.

Things you would do in the game to keep it simple are:

- Connect to the server

- Move the player with the keys

- Possibly press space bar to attack

- Send a chat message

But what would happen if a datagram from any of these situations get lost?

What would you do?

1. Connecting to the server .

If you send a UDP datagram to the server, the server would take your IP and port and then create a player

based on an ID it gives you, this it how it identifies you every time it receives a datagram from you.

But what if upon "connection" (not actually a connection, just a udp datagram that says 'make me a part of your server'), this initial datagram gets lost. Is it right to say that you would just resend it if after a certain period of time you did not receive a reply back from the server?

2. Moving the player/attacking

If at any time we move the player/press a movement key/attack key, we would send a keystroke

to the server telling it what key we pressed/released.

The server would then relay this to all the other clients.

But what if this keystroke datagram gets lost? Either upon client->server or server->clients.

Because keystrokes are happening all the time, is it efficient to just resend it all the time?

Or would we just ignore the fact that it got lost along the way because there's a chance that if you press a key again it will most likely make it the next time?

3. Sending a chat message

This would be something that MUST reach the server.

If it got lost along the way, after a certain period of time if we did not receive a reply from the other side/receiving end, do we re-send it?

Is it okay to just keep resending datagrams if we know after a certain period of time it did not reach?

And also, what about confirming if a datagram got successfully sent?

If client sends a chat message to the server, and the server receives it, should the server send a reply back to the client to confirm that it received it? What would happen if that reply got lost, what then?

Advertisement

http://gafferongames.com/networking-for-game-programmers/

Read all of these. Then come back if you have more questions.

Sean Middleditch – Game Systems Engineer – Join my team!

If you're sending "real time data" then as time passes the data changes so you just send a stream of packets and if one goes missing the "first" thing you do is ... nothing.

You just use the latest packet of data and call it good.

As you get into more advanced "lag hiding" you will likely add some interpolation, once you do that you would want to take into account the additional time that has elapsed if you miss a packet.

Send out packets at a nominal fixed rate and put a counter in the packets so you'll know if and how many packets you have missed.

If you need to know the data got there, then use TCP.

You can use multiple UDP & TCP ports if you need/want to (don't go crazy).

- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

The Quake3 UDP model constantly sent deltas/differences from the client's last known state to the current game state. The client responds by telling the server which state it's currently up to.
If any data gets lost, everything keeps working fine. The only issue is that both the client and the server need to keep around many frames of old game states (as old as the oldest acknowledgement) so that these deltas can be constructed and applied.

e.g. some traffic could look like:
Client -> I'm at state 6
Server -> Here's delta for 6->8
Server -> Here's delta for 6->7 [ARRIVED OUT OF ORDER - ignored by client, is already at state #8]
Client -> I'm at state 8 [DROPPED - server still thinks client is at 6]
Server -> Here's delta for 6->9 [no problem, client can update from 6 to 9 still, even though it's at 8 currently]
Client -> I'm at state 9
Server -> Here's delta for 9->10 [DROPPED - client experiences a small bit of lag]
Server -> Here's delta for 10->11
Client -> I'm at state 11

Lots of such solutions exist, but you will build a good solution more quickly if you realize that it is really more a matter of latency than lost data.

Ideally, every update is absolute and not one in a string of dependent delta's. This way, if 1/2 of your packets are lost, the game continues to work fine but updates are roughly half as fast.

For player movement, this is often sufficient.

But what if you lose unique events like shots fired or attacks made?

These require some sort of ACK and possible re-send, or perhaps even duplication. But even in this case, if your server receive a message essentially saying "I shot Joe seven seconds ago," what should you do with it? You should ignore it. Game-play is better served by a spurious "miss" than by a seemingly random "hit."

Many architectures exist for handling these, but lets say you have just these two things to consider: movement and "events" (shots, attacks, etc.).

You can perhaps send movements (actual absolution position, rotation, etc.) on some regular schedule (NOT just when they change in the client, but every 1/30th of a second, for e.g.). If you are worried about a chatty protocol, add an ACK mechanism and stop sending once an [un-changing] position is acked.

Then, you can send "events" as soon as they happen and then every [n] ms thereafter until ACKED by the recipient. [n] would be something you could compute from your percieved latency on the connection.

So, for example:

* Every 1/30 second you sent a position update, and you just rely on the receiver to get them

* You monitor acks on acked communication and determine the round-trip-time (rtt) is 20ms

* An event (such as a shot) occurs, so you send E + {data about E}, and you starta timer to go off in [rtt] ms (20 ms)

When the timer goes off, you check to see if you got an ACK for E, and if not, you send it again.

If other events occur, you append them to the list of events to be sent when the timer goes off.

Eventually the events will all be acked and your event list will be empty, or you will decide it is too late (perhaps 1-2 seconds).

In general, a good test is to write yourself a proxy that randomly discards 1/2 of your packets and make your game play well using that. In practice, the internet is very reliable where UDP is concerned, since most routers will prioritize UDP over TCP packets, yet most packet loss is due to congestion and that congestion is almost always TCP traffic.

In general, you want to structure your netcode in a series of layers:

1) The connection layer, which takes care of ensuring that the client sees the server, and the server sees the client. This may also gather connection statistics (round trip time, etc)
2) The framing layer, which allows you to send multiple messages in the same single network packet.
3) The messaging layer, which takes care of delivering different kinds of messages with different reliability (best-effort, discard-earlier, always-delivered, always-in-order-delivered)
4) The entity layer, which is where specific messages get routed to specific in-game objects/entities/smurfs. This could also have knowledge about things like prediction, latency correction, etc.

Even if you use TCP, there is some work to do in 1), typically to keep the simulation ticks between client and server roughly in sync, and to detect whether the connection has "stalled out."

In 2) you typically end up with some kind of serialization, or type-size-data triplets, on the wire.

In 3) is where you use different mechanisms to achieve the different levels of reliability needed for your game, although on top of TCP, everything will be in order, so that'll be a no-op. It's important that the layer that knows about entity needs (4) chooses the right option. Chat messages should probably be "always delivered once" but not necessarily in order; state updates are typically "discard earlier" and re-sent on a round-robin basis, rather than sent with any specific kind of reliability. All you need there is to make sure that a re-ordered packet doesn't overwrite newer receive state with older.

The rest of your question has to be answered in the context of the particular game and simulation you're implementing, because which specific solution you choose depends on which specific feel you want in your game.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement