One system too rule them all :)

Started by
7 comments, last by TheGilb 16 years, 2 months ago
Hello all, I've seen different network libraries have one system for sending the packages, whether they are reliable or not, sequenced, ordered etc. I was wondering how they do this. How can you make unreliable and reliable packages go through the same system if they have the same set sequence numbers ? Also how can I make some messages go ordered and some not ordered? I've heard people use ordering channels, does that have anything to do with it ? I've tried setting "ORD" flag when I want to send something ordered and then in packetsender check for the "ORD" flag and not check for ordering if it does not have that flag but that didn't work out too well. Oh btw all of this is on reliable udp. (just to make sure you know that Iam not writing some TCP implementation on some wierd OS or so :)) Anyway please let me know if you know a good way too make this work. It would help me a lot. Thanks! Ponl
Advertisement
I can't comment on other libraries but speaking from experience the way I do it is by putting the relevant information in the packet header. So if the packet type is 1 it might be unreliable (no special processing required), if the packet type is 2 it might be reliable then it has a sequence number which requires special processing, etc..

To send messages and guarantee they arrive in order you use a sequence number which you increment every time you send. Whenever you receive an ordered packet you record the sequence number, if the sequence number of the received packet is larger than the previous highest then you deliver it, otherwise you discard it or whatever you want to do with it. Without the ordering code when sending packets over the internet via udp they will potentially arrive out of order, that isn't much of an issue if you're testing on a lan, but there are no guarantees with udp.

To send messages reliably (guaranteed delivery) you need to keep sending the message until you receive an acknowledgment (the client sends the ack upon receipt of reliable packets). Even the acknowledgment can be lost, arrive late or out of order so be sure to handle these cases as well.
Okay thanks so basicly you just fill in a flag for every feature and at each end you check and put in the appropriate buffers and ack if necicary etc. Okay that would be a great system.

Thanks a lot!
Cheers
Ponl
Quote:How can you make unreliable and reliable packages go through the same system if they have the same set sequence numbers ?


You have to realize that there are two levels of indirection. One level is that of the actual network packet. This will have a sequence number (for ack), global timing information, etc. Then, there is a level of reliability channels within that. When unreliable packets get acked, you know that all the reliable messages in that packet made it through.

Now, for a very simple implementation, you will hold all reliable messages on a given channel, until you have an ack for the packet that that message went through in, or some later packet, at which point you assume the packet was lost, and re-send. You will still need a sequence number per reliable sub-stream to avoid double delivery.

One step up, you allow an outstanding pipe of reliable messages per channel, and re-order (and wait for re-sends) on the receiving side, on a per-channel basis.

Note that the sequence numbers on the reliable channel messages are only for ordering and avoiding duplication; they are not needed for acknowledgement, because you can derive that information because you know which reliable messages went in what overall packets.
enum Bool { True, False, FileNotFound };
First of all thank you for your explanation.
But I don't think I fully understand :

Quote:
When unreliable packets get acked, you know that all the reliable messages in that packet made it through.


So there are messages inside packets, why is that ? Is that for split packeges or ?

Quote:
Now, for a very simple implementation, you will hold all reliable messages on a given channel, until you have an ack for the packet that that message went through in, or some later packet, at which point you assume the packet was lost, and re-send. You will still need a sequence number per reliable sub-stream to avoid double delivery.

One step up, you allow an outstanding pipe of reliable messages per channel, and re-order (and wait for re-sends) on the receiving side, on a per-channel basis.

Note that the sequence numbers on the reliable channel messages are only for ordering and avoiding duplication; they are not needed for acknowledgement, because you can derive that information because you know which reliable messages went in what overall packets.


Do you have a source code example of this because it is not really making sence to me ?

How I plan to do it now is I keep a buffer for sending and in that buffer I put all the segments I am going to send. For the size of the window I send all the segments. I pop the front if the front is unreliable or if the front has been acked.

On the receiving side I keep a buffer of the segments I get, For the size of the window I check whether the segment is reliable or not, if it has to be ordered I put it in order and deliver the packets.

Would this work or is this a bad way of doing this.

Thanks!
Ponl
Quote:So there are messages inside packets, why is that ?


If your network tick rate is X, you only want to send X network packets per second. This is to reduce the impact of the 28 byte overhead of a UDP datagram -- if you send 100 packets a second, that's almost 3 kilobytes just for IP/UDP headers! Thus, you queue the messages that are going out, and X times a second (where X varies from 2 to 30 depending on game kind) you pack all the messages in the queue (that you can fit) into a single datagram, and send that as a packet.

This also means that you only need a single application-level frame header with timing information, user credentials, etc, per network packet, so there's even more savings than the 28 bytes per packet compared to sending a UDP datagram per message.

No, I don't have sample code for an implementation available to show. I would recommend reading one of the available libraries, such as ENet, to see how they did it. I don't know if ENet specifically merges messages into packets, though.
enum Bool { True, False, FileNotFound };
Merging message is an old trick. Also on consoles there is an extra overhead with each packets for the encryption layer. Add to it your own packet headers, then you can easily run into a 50 byte overhead per packet. That's a lot. In fact, in many cases, the packet header will be larger than the data you send. So if you have lots of little messages to send over one frame, you'd better bundle them as tight as possible before sending them.

Everything is better with Metal.

Okay that explains the packing of segments, its indeed logical to send more as one segment. I just never really thought about this this ways since it increases latency but its a great way.

Quote:
No, I don't have sample code for an implementation available to show. I would recommend reading one of the available libraries, such as ENet, to see how they did it. I don't know if ENet specifically merges messages into packets, though.


I'll try to turn ENET around and to learn the optional reliable flag (I read on the site it has this). I think ENET is indeed a good library to learn from since it has a small codebase which does its job.

Anyway, thanks for all the help guys!
Ponl
If you think about it, combining several packets into a single packet for dispatch doesn't really increase latency if you think about game time as frames. So machine 1 dispatches a packet at a fixed rate - say 50Hz (or 50 times a second) - at the end of each frame and machine 2 reads from the socket at the start of every frame. If a packet is received then it's enough data at once to ensure the game state is synchronised for that frame. If you were sending individual packets for each object then some might get lost while others get through which would lead to only a partial synch. Most of the time, if an object is important enough that you need to transmit data about it on the sending machine over the network then it's important enough that when everything else synchronises on the receiving machine then it needs to be synchronised at the same time. The latency that you are thinking of is negligible assuming the network layer always updates at 50Hz. A common trick is to put the networking layer on a separate thread to ensure an independent update rate.

This topic is closed to new replies.

Advertisement