TimonPost / laminar

A simple semi-reliable UDP protocol for multiplayer games
815 stars 66 forks source link

Thinking of packet processing logic. #39

Closed TimonPost closed 5 years ago

TimonPost commented 5 years ago

I am thinking of the packet workflow under which you can think: easiest way for creating packets and sending them over to the other side with all the options that a packet could have like encryption etc.

Take the following things into consideration:

  1. Options like encryption/decryption
  2. Options for different reliability strategies: Unreliable, ReliableUnordered, Sequenced ReliableOrdered.
  3. Must be configurable.
  4. Must be easily extendable.
  5. The user does not have to be bothered with all the different network tactics and implementations on how to send packets. The only thing a user should care about is, sending bytes!

Here I have a draft on how to separate packets of different types. And on how we can convert a simple user packet to some more complex packet types about which the user should not care about.

https://gist.github.com/TimonPost/303c53fa1454d724120a1173b04a9745

Now working on processing the bytes of each packet this issue will be updated soon.

OvermindDL1 commented 5 years ago

In addition to priorities to determine which to focus on sending first, and also channels so you can have multiple 'ordered streams' in progress at the same time.

TimonPost commented 5 years ago

There was once some kind of implementation of channels. But some folks did not like opening different UPD sockets for different kind of implementations. See this PR

OvermindDL1 commented 5 years ago

But some folks did not like opening different UPD sockets for different kind of implementations.

Because you shouldn't need to. It should be part of the application packet header, using a single byte is well worth channel support for 255 different channels for anything sequenced or ordered (and unordered doesn't need it at all).

TimonPost commented 5 years ago

@OvermindDL1 I like the channel idea but am not sure if I understand it right. Can you be more specific with the channels a bit?

Currently, how I am imagine this is having channels who will process each packet on its own way. Based on the id of the packet it will be processed by a certain channel. Then my next questions are:

  1. How is the user able to get the packet from the channel.
  2. Does the user have access to the channel contents? And processing stuff.
  3. How does the user get the newest packets from the channels?
  4. Do you have some example of the channels Raknet is using? I have been wandering around in their code but not have seen anything related to channels.
OvermindDL1 commented 5 years ago

A channel isn't that complex at the network layer, a channel is just a unique ID counting for sequenced/ordered packets.

As in you could, for example, by sending a file (Ordered) on channel 0, sending Sequenced positional data on channel 1, sending Ordered chat data on channel 2, etc...

It is just a way to break up ordered data that does not have dependencies on each other, like the file uploading ordering doesn't matter whatsoever with positional or chat data packets, and by sharing the ordered ID with them then it could potentially cause a (potentially massive) delay until the positional or chat data arrives, where with channels they can be interleaved on the wire with impunity. RakNet handled this well if you want an example. The accessing of packets is transparent to it, you only specify the ordering channel upon 'sending' a packet, and in something like Raknet, which supports 255 channels by using a byte on the packet for it (with 0 reserved for RakNet metapackets like ping and CRC checks and so forth) then the receiving side knows what each ordering channel is meant to be, each ordering channel having it's own sliding window and all. It's similar to how the SCTP low-level protocol works (I really wish SCTP took over TCP decades ago...).

That's an idea, there are SCTP (SCTP is on the same level as UDP or TCP, Windows never supported it though hence why it never become popular) usermode implementations emulated on UDP out there, could look at them for ideas as well (multi-homing support and all in it), though SCTP doesn't support unordered or sequenced packets, the channels idea is rock solid in it.

OvermindDL1 commented 5 years ago

In the Raknet docs it speaks of the costs (my bolding): http://www.raknet.net/raknet/manual/systemoverview.html

A message is the data you send from the game. All messages you send to RakNet between RakNet update ticks are grouped together in one datagram. So if you send 1 message only, then the overhead is 1 datagram + 1 message. If you send 5 messages, then it's 1 datagram + 5 messages. If you send 1 message, but it's 10 times bigger than the MTU, then it sends 10 datagrams, each containing 1 message (the message gets split up)

OvermindDL1 commented 5 years ago

At http://www.raknet.net/raknet/manual/detailedimplementation.html you can see in the Sending Data section:

The best way to illustrate sending data is with an example: const char* message = "Hello World";

To all connected systems: peer->Send((char*)message, strlen(message)+1, HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_RAKNET_GUID, true);

The first parameter is your data and must be a byte stream. Since we're sending a string, and a string is a byte stream, we can send it directly without any casting.

The second parameter is how many bytes to send. In this example we send the length of the string and one more for the null terminator.

The third parameter is the priority of the packet. This takes one of three values: IMMEDIATE_PRIORITY, HIGH_PRIORITY MEDIUM_PRIORITY LOW_PRIORITY

IMMEDIATE_PRIORITY messages signal RakNet's update thread to update immediately. Assuming bandwidth is available, they get sent immediately. HIGH_PRIORITY, MEDIUM_PRIORITY, and LOW_PRIORITY messages are put into a buffer. The next time RakNet's update thread ticks (which is every 10 milliseconds) those messages get sent. Those 3 priorities can be more efficient for bandwidth, because if multiple messages can be aggregated into a single datagram, RakNet will transparently do so

Twice as many messages get sent out for each higher priority. So if messages of all priorities were waiting to go out, 8 IMMEDIATE_PRIORITY, 4 HIGH_PRIORITY, 2 MEDIUM_PRIORITY, and 1 LOW_PRIORITY would be sent. Obviously however, if only LOW_PRIORITY messages were waiting at all, then those messages would all go out as fast as possible.

The fourth parameter takes one of five major values. Lets say you send data 1,2,3,4,5,6. Here's the order and substance of what you might get back:

UNRELIABLE - 5, 1, 6 UNRELIABLE_SEQUENCED - 5 (6 was lost in transit, 1,2,3,4 arrived later than 5) RELIABLE - 5, 1, 4, 6, 2, 3 RELIABLE_ORDERED - 1, 2, 3, 4, 5, 6 RELIABLE_SEQUENCED - 5, 6 (1,2,3,4 arrived later than 5)

For more details on this refer to PacketPriority.h.

The fifth parameter to Send() (0 in this example) is which ordering stream to use. This is used for relative ordering of packets in relation to other packets on the same stream. It's not important for now, but for more information on this refer to the Sending Packets section.

The sixth parameter (UNASSIGNED_RAKNET_GUID), is the remote system to send to. UNASSIGNED_RAKNET_GUID is a reserved value meaning "no-one in particular". This parameter means one of two things : either who you want to send the packet to, or who you don't want to send the packet to, depending on the value of broadcast, which is the last parameter.

The seventh parameter (true in this example) is whether to broadcast to all connected systems or not. This parameter works with the sixth parameter. If broadcast is true, then the sixth parameter specifies who not to send to. If it is false, then it specifies who to send to. If we want to broadcast to everyone, then we just specify UNASSIGNED_RAKNET_GUID. This works out when relaying packets, because the Packet::systemAddress field in the packet you get will specify who the sender is. We can relay the packet to everyone BUT the sender, which makes sense since we usually don't want to send the same information back to the person who just sent it to us.

OvermindDL1 commented 5 years ago

And you can see how send is defined at: http://www.jenkinssoftware.com/raknet/manual/Doxygen/classRakNet_1_1RakPeerInterface.html#6d9a5415556a9c138854cc05c707b8e7

uint32_t RakNet::RakPeerInterface::Send(
    const RakNet::BitStream * | bitStream | const char *,
    PacketPriority | priority,
    PacketReliability | reliability,
    char | orderingChannel,
    const AddressOrGUID | systemIdentifier,
    bool | broadcast,
    uint32_t | forceReceiptNumber = 0
)

With the documentation of:

Parameters:

In/Out Type Description
[in] bitStream The bitstream to send
[in] priority What priority level to send on. See PacketPriority.h
[in] reliability How reliability to send this data. See PacketPriority.h
[in] orderingChannel When using ordered or sequenced messages, what channel to order these on. Messages are only ordered relative to other messages on the same stream
[in] systemIdentifier Who to send this packet to, or in the case of broadcasting who not to send it to. Pass either a SystemAddress structure or a RakNetGUID structure. Use UNASSIGNED_SYSTEM_ADDRESS or to specify none
[in] broadcast True to send this packet to all connected systems. If true, then systemAddress specifies who not to send the packet to.
[in] forceReceipt If 0, will automatically determine the receipt number to return. If non-zero, will return what you give it.

Returns:

0 on bad input. Otherwise a number that identifies this message. If reliability is a type that returns a receipt, on a later call to Receive() you will get ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS with bytes 1-4 inclusive containing this number

TimonPost commented 5 years ago

Thanks for the detailed reaction, forgot to react on this issue. I am currently investigating RakNet. O man it is so much code :). But I have to take note here the most of RakNet its features are going way beyond or usecases. The only thing we need is the reliability implementation.

The file containing the most concerns for us can be found here.

OvermindDL1 commented 5 years ago

Thanks for the detailed reaction, forgot to react on this issue. I am currently investigating RakNet. O man it is so much code :). But I have to take note here the most of RakNet its features are going way beyond or usecases.

Correct, RakNet is a LOT of things built on a comparatively simple (okay not really that simple, but still) network interface, should just focus on that to start with. I can still pick up the code base pretty quickly if you have questions about anything in it though.

The only thing we need is the reliability implementation.

The file containing the most concerns for us can be found here.

Not just the reliability implementation, but marking messages as unreliable along with channels and so forth are all part of the reliability setup as some packets you just don't care if get there (rapid position packets between occasional reliable position updates of an object for example), and the multiple channels mean that a single dropped packet won't hold up the other channels (or if a single channel gets overwhelmed like when sending a file for example), etc... etc...

TimonPost commented 5 years ago

This kind of is solved #65