Mixing reliable and unreliable messages

Started by
8 comments, last by hplus0603 11 years, 11 months ago
Hi,

I am currently working on a network abstraction layer that will be able to deliver reliable and unreliable messages over UDP.
Each UDP packet contains a user id, the current sequence number, an ACK field and an ACK bitfield (as described here) and a payload among other things like the current tick.
The payload contains the games messages. Each message is simply a message type id followed by the message data.

I now want to be able to send certain message reliably and unreliably, but that seems to be very tricky if I have packets that contain mixed message types, due to the fact that the ACK field refers to the packet and not to its content. Three solutions came to my mind:

  1. Send all packets reliably (most simple solution, but I am not sure how big the effect on network performance would be)
  2. Don't mix message of different reliablity type in the packets payload. Have packets that only contain reliable messages and packets that only contain unreliable messages, but let unreliable packets be able to acknowledge reliable packets. The question here would be, how frequently should reliable and unreliable messages be sent? Sometimes there might even be no reliable messages to be send, because they're only created for consistent events (in my specific use case). If I'd send let's say 20 packets per second, what percentage of those should be reliable packets that contain only reliable messages?
  3. Have a sequence number and a reliability flag for each payload and treat them like single packets, but bundle them together before sending them over the network.


What would be the best approach to do that? Is there maybe a fourth alternative that I didn't think of that could solve all my problems?
Thanks in advance.

Greetings,
Pfaeff
Advertisement
Hi, I'm currently working on pretty much the same thing :)

my approach is to have the first 16 bytes in each package as a header, consisting of four integers.
0 = hash tag to sign the package (hash of package content + shared session key)
4 = package confirm (the last received package from the connected peer)
8 = package id where value 0 - 9 are reserved (0 => unreliable)
12 = session id

In short:
If I receive a package with package id = 0 I'll verify the content of the package, copy the confirm value to my outbox (so i know which packages not to resend), and finally pass the remaining data on to the user.
If I receive a reliable package (package id > 9) I'll do the above steps, and set a flag to confirm the last received package. On the next send call I'll set the confirm value in all outgoing messages to latest received package and send them. If my outbox is empty I'll send an unreliable package to confirm.

To answer your questions:
1 )
Since you are using UDP you might as well use the power of UDP - besides easy NAT punch through, so I would not recommend you to send everything reliable.
2 )
If you want the unreliable packages to be fast you should not bundle them with reliable ones. Unless you are going to send an reliable package at the same time. Then you might as well bundle them (but not necessarily the second time you send the reliable package (if first try should fail)).
Yes you should defiantly acknowledge reliable packages in your unreliable packages.
Knowing nothing of your game, and not being that experienced in networking I don't know the answer for the last question in (2)
3 )
The answer for this is pretty much the same as for (2). I don't - I neither deals with payloads, but only packages.

Hope this helps :)

Each UDP packet contains a user id...

Are you expecting for one client to be serving multiple users? The user id can be inferred from the IP / port of the packet. Including a user identifier in packets only yields to vulnerabilities where the user id is accidentally used unverified, allowing clients to impersonate one another.


What would be the best approach to do that? Is there maybe a fourth alternative that I didn't think of that could solve all my problems?
[/quote]
If the peer records which reliable messages are associated with each packet, when a packet is acknowledged then all reliable messages that were included in this packet can be marked as delivered.

[quote name='Pfaeff' timestamp='1335700705' post='4935803']
Each UDP packet contains a user id...

Are you expecting for one client to be serving multiple users? The user id can be inferred from the IP / port of the packet. Including a user identifier in packets only yields to vulnerabilities where the user id is accidentally used unverified, allowing clients to impersonate one another.
[/quote]
I also thought about this and I think you are right. It would be better to just identify the users based on the IP address.



What would be the best approach to do that? Is there maybe a fourth alternative that I didn't think of that could solve all my problems?
[/quote]
If the peer records which reliable messages are associated with each packet, when a packet is acknowledged then all reliable messages that were included in this packet can be marked as delivered.
[/quote]
Why didn't I think about this ohmy.png ? The sender just needs to know which messages where in the packet that got acknowledged and mark them all as received. This seems to be the best solution. Thanks a lot.
[quote name='Pfaeff' timestamp='1335700705' post='4935803']Each UDP packet contains a user id...
Are you expecting for one client to be serving multiple users? The user id can be inferred from the IP / port of the packet. Including a user identifier in packets only yields to vulnerabilities where the user id is accidentally used unverified, allowing clients to impersonate one another.[/quote]
Looks like I'm shaving four bytes off my header as well then :)
1 byte for last ack ID.


- MTU = 536 - 1500
- 1 byte for last package, 256 unique values
- 137,216 - 384,000 bytes of data

At 5kB/sec, it takes 20-40 seconds till wraparound.

To detect wraparound, count number of sent packages locally with some larger counter, 32 bit int or so. Add that to checksum. If silent wraparound occurs (exactly 256 packages lost), the hash won't match.
----
1 byte for package ID:

They contain only control data (connect, disconnect, ping, login, etc...). The rest goes into payload.

----
Checksum: 2 or 4 bytes


Which means the entire protocol header is 4 or 6 bytes.

Session ID and all that isn't needed, as mentioned.
Ok, for my new implementation I've created a six bytes header with a checksum in the first four bytes, package id in the next and last confirmed package id in the last byte. the last ten values of the package id is dedicated to special (i.e. FAST(unreliable), SYN, SYNACK, etc.).

But apparently (as far as I can see) there is no way to get the MTU through Java - I don't have access to IP headers or DF-bit - So would you recommend me to go for the lowest MTU? Since the aim of my network library is fast updates not file transfer, It would primarily be very long chat messages that would be split up even if I go for max package size of 500.
Or is the percentage of small-MTU-networks so low that practically no one would be affected by the overhead of package fragmentation, if I go for package size of 1460? (considering that most packages wont be that big anyways).

Bonus question:
For checksum I'm using CRC32, would CRC16 be enough? or should I find some thing more secure than CRC32?

Edit: Also, sorry for going a little off the original topic.

[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]

[background=rgb(250, 251, 252)]For checksum I'm using CRC32, would CRC16 be enough? or should I find some thing more secure than CRC32?[/background]

[/font]
[/quote]
Checksums aren't designed to be secure. They are designed to detect accidental corruption.

If you think need "security", then you first need to define your goal. Once you have a goal, then the technical measures you can take to try achieve this goal can be discussed. Such discussion should not occur in this thread (as you have realised).

Sorry, I meant is the collision rate considered too high? I would guess no, but this is my first time playing with UDP, so I'll rather be certain than have to change it later.

I also thought about this and I think you are right. It would be better to just identify the users based on the IP address.



You need to use both the remote IP address and the remote port to uniquely identify a remote player. Else, two players behind the same NAT gateway would be confused with each other (same house, same company, same school, same ISP, ...)
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement