Closed TimonPost closed 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.
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
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).
@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:
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.
In the Raknet docs it speaks of the costs (my bolding): http://www.raknet.net/raknet/manual/systemoverview.html
Per datagram:
Per message:
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)
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.
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
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.
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...
This kind of is solved #65
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:
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.