Data Coordination Between Server & Client

Started by
34 comments, last by roadkillguy 12 years, 9 months ago

Maybe I'm not understanding something.
[/quote]
I think so.


Do I need variable reception ports on the client?
[/quote]
You don't need them, but it is better to use them. The server should certainly not expect the client to use a particular port. That assumption would break with most NAT setups.


My qualm is that a packet might be on port 2000 and be going to IP 203.53.100.10, but will never make it to 192.168.1.108 like it was supposed to.
[/quote]
Packets aren't on ports. They have two ports, their source and destination. I assume you're saying "The packet is sent to port $DESTINATION_PORT and I want it to get to $SERVER_IP_ADDRESS but it doesn't even make it to $LOCAL_ROUTER_IP_ADDRESS". Yes, with UDP you have to deal with packet loss. So your client must keep sending until either enough time has passed to convince you there is no point continuing, or until it is successful. That isn't the only problem with UDP, you need to be able to handle duplicate and out-of-order packets too.


Each client on the same IP could then have a different reception port and the same sending port, but would have to be port forwarded manually.
[/quote]
Forget port forwarding. Port forwarding is mostly used for taking a listening service on a machine with a given private IP address and (typically) well known port, and making it available by instructing the router/firewall/NAT/whatever to make incoming messages on the public IP address and well known port get to the private machine.

For a client, it simply shouldn't be required at all.
Advertisement
Hmm I didn't know there were different destination and host ports. Either way, how does a packet tell the router which local ip address to send it to? Does it send it to every local ip?

In SDL_Net, there is a UDPpacket struct. It has two destination fields --Address, and port. You then call SDL_Net_UDP_Send(UDPsocket socket, UDPpacket packet) to send the data (which is also in that struct). Unfortunately, a UDPsocket is basically a port binding, and I'm not sure where else a local IP address would come into play.

Is Enet easily compiled with mingw? I currently cross compile to windows from linux. I'm using SDL_Net.


I believe so, as it started out on Linux. However, if your target is Windows, you should download and install Visual C++ Express 2010. It's a vastly better development environment for Win32 applications.



Do I need variable reception ports on the client? My qualm is that a packet might be on port 2000 and be going to IP 203.53.100.10, but will never make it to 192.168.1.108 like it was supposed to. Each client on the same IP could then have a different reception port and the same sending port, but would have to be port forwarded manually. Maybe I'm not understanding something.
[/quote]

You apparently need to learn a bit more about how NAT works. Once you understand that, you need to understand what port forwarding does, and what NAT punch-through is, and when it's needed. There are links in the FAQ that might help you. In brief, your router will translate the "from" part of outgoing packets from 192.165.whatever:whatever to some-ip:some-port. It will then re-translate incoming packets to some-ip:some-port back to 192.168.whatever:whatever and send them back in. The key is that the first packet has to be outgoing, so that the translation table can be established. Also, the server needs to use recvfrom() to receive the address that it should return packets to, rather than try to put addressing data as part of the payload of the application-layer protocol (like FTP and the H. VoIP protocols do).
enum Bool { True, False, FileNotFound };
Subconsciously I was being told that there was data being lost.. I guess I'll just have to trust the routers then? I'll definitely read more into that.

I've now designed a pretty cool packet checking system using a list of packets and a pthread. I resend packets every 2 seconds if no response is seen from them. Is that a good number? I think I'll remain with UDP.

Also, I'm really liking makefiles and command line g++. I run Ubuntu on a netbook, so VC++ would be rather difficult to install and use (I have used it before). I'm not really one for windows wink.gif.

I think I have a pretty good Idea of what to do now. Thanks to everyone who answered my questions.
2 seconds might as well be 2 years for most fast paced action games (e.g. the kind it makes sense to use UDP for at all). You'll want to be resending data within a handful of network "ticks", the exact value is something you're going to have to tune to your game.

2 seconds might as well be 2 years for most fast paced action games (e.g. the kind it makes sense to use UDP for at all). You'll want to be resending data within a handful of network "ticks", the exact value is something you're going to have to tune to your game.


Ah okay then. Is there a specific way I should be coordinating network ticks? Should all packets have a tick in the header? Waiting on a network tick packet would be kinda laggy.

In other words, how should I compensate for a faster client than server? If the client gets ahead (if the ticks are on a timer say) how would they know they're ahead and that the server data isn't just outdated?
I don't know =] It depends heavily on your game. I would refer you again to question 12 in the forum FAQ, which will give you hints about how other deployed games handled this.

[quote name='rip-off' timestamp='1310818055' post='4835962']
2 seconds might as well be 2 years for most fast paced action games (e.g. the kind it makes sense to use UDP for at all). You'll want to be resending data within a handful of network "ticks", the exact value is something you're going to have to tune to your game.


Ah okay then. Is there a specific way I should be coordinating network ticks? Should all packets have a tick in the header? Waiting on a network tick packet would be kinda laggy.

In other words, how should I compensate for a faster client than server? If the client gets ahead (if the ticks are on a timer say) how would they know they're ahead and that the server data isn't just outdated?
[/quote]

What I like to do is to use a single byte for a packet number. Each packet I send uses the next higher value, wrapping around at the end.
Also, with each packet, is the packet number of the last packet I received from the other end. Additionally, there is a bitmask of the eight packet numbers before that that I've received.
Thus, if I receive packets 10, 11, 12, 14, 15, 16, 17 and 19, then the received data will be:
19 == 0x13 (last seen number)
0b11011110 == 0xde (bit 0 means packet 18, bit 1 means packet 17, bit 2 means packet 16, ...)

When the other end sees any packet "not seen" in the lower bits, it can take the information that's needed from that packet, and stuff that information into the next packet. Drop the information that's not needed from the dropped packet, and don't re-send the entire packet.

This system will automatically adjust to whatever the round-trip time is, up to a window of 255 packets (with conservative loss estimation) or 9 packets (with perfect loss information). However, you may want to limit the amount of send-ahead to a fixed small number of packets -- adjust the send rate so there's never more than 3 oustanding un-acked packets, for example.
enum Bool { True, False, FileNotFound };

What I like to do is to use a single byte for a packet number. Each packet I send uses the next higher value, wrapping around at the end.
Also, with each packet, is the packet number of the last packet I received from the other end. Additionally, there is a bitmask of the eight packet numbers before that that I've received.
Thus, if I receive packets 10, 11, 12, 14, 15, 16, 17 and 19, then the received data will be:
19 == 0x13 (last seen number)
0b11011110 == 0xde (bit 0 means packet 18, bit 1 means packet 17, bit 2 means packet 16, ...)

When the other end sees any packet "not seen" in the lower bits, it can take the information that's needed from that packet, and stuff that information into the next packet. Drop the information that's not needed from the dropped packet, and don't re-send the entire packet.

This system will automatically adjust to whatever the round-trip time is, up to a window of 255 packets (with conservative loss estimation) or 9 packets (with perfect loss information). However, you may want to limit the amount of send-ahead to a fixed small number of packets -- adjust the send rate so there's never more than 3 oustanding un-acked packets, for example.



That's actually pretty cool. Do you use a received packet buffer or something? In my game, the client needs to know about the terrain data as soon as they join. Unfortunately, waiting for a packet to be accepted (by waiting for the ack) would not only block, but would also destroy any other packets coming in (They're ignored and have no other place to go). I could solve the blocking with a thread or two, but I'm still limited by my getData function.

My other question is, how do you/should I send my packets? It sounds like you send gamestates? from the server, and input from the client. I'm not sure how to organize a gamestate packet. How do you determine whether data is or isn't needed? Right now, I send individual packets and messages. (It's kinda left over from when I used TCP)

That's actually pretty cool. Do you use a received packet buffer or something?


No, I just remember the last 8 packet IDs received (or, rather, a bit mask, and a counter). If a packet comes in out of order, I deliver reliable messages within the packet if they haven't already been delivered, but not unreliable messages.
Note: I pack a bunch of messages into one packet, and serial numbers are per packet. In the full realization of this system, I had send queues of various kinds:
- a cyclic queue for objects that I "baseline" (send snapshots for)
- a queue of outgoing reliable messages, per reliable message stream (each stream is separately sequenced)
- all received input commands for a given time step, to echo out to all players

The receive side for reliable message streams will need to use a queue if the guarantee is "guaranteed in-order delivery," but if the guarantee is just "guaranteed delivery, may be out of order," then I don't need that. The sending side will aggressively re-send reliable messages that were in a packet that was sent and later not acknowledged in order (a '0' in the bit mask). Because of this, "guaranteed delivery but not ordered" streams don't actually deliver messages that come in in out-of-order packets, because they will be re-sent -- so I only deliver "reliable, out of order" messages that are in a packet that is received right after the previous packet, if that makes sense. It makes logic simpler, but may sometimes cause some messages to be delayed more than strictly necessary in the face of out-of-order packet delivery. That being said, out-of-order delivery is very uncommon -- dropped packets are much more common IMO.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement