Skip to content

fracturestudios/radon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Radon is a C++ socket wrapper and accompanying protocol. Radon ensures packets are delivered once, reliably, but allows packets to be received out-of-order.

Motivation

In real-time networked simulations, it is desirable that packets containing simulation state are sent reliably (i.e. retransmitted if not acknowledged). Although retransmission is possible, ideally, each packet should only be delivered to the caller once.

TCP accomplishes both of the above. However, since TCP is stream-oriented, it ensures packets are delivered to the receiver in the same order that that they were generated by the sender. In network simulations, this can cause undue latency, and packets which have already been received must wait until all packets with lower sequence numbers have also been received.

UDP doesn't have this latency issue; however, it also does not handle packet retransmission.

Radon handles sequencing your packets, automatically acknolwedging them, and retransmitting packets that appear to have been lost.

Example

#include <rn/endpoint.h>
#include <rn/socket.h>

// ...

void my_init_socket(QUdpSocket *udp)
{
    RnSocketOpt opt = { 0 };
    opt.Timeout = 100;  // In milliseconds
    opt.MaxRetransmits = 3;

    m_sock = new RnSocket(RnEndpoint::fromQt(udp), opt);
}

// ...

void my_send_data(const std::string &data)
{
    m_socket->send((void)data.c_str(), data.size());
}

// ...

void my_update_frame(float dt)
{
    RnSocket::update(dt);

    RnPacket *packet = nullptr;
    while (packet = m_sock->recv()) {
        my_process_data(packet->data(), packet->size());
    }
}

Compiling

Radon uses Qt5 as its underlying socket API. In order to build Radon, you must have Qt 5.x set up on your system.

With Qt configured, all you need to build Radon is run

qmake && make

This builds a static library named radon.lib or libradon.a, depending on your host platform.

Protocol Details

Radon sockets communicate over UDP. Each UDP packet contains the following structure:

+-------------------+
|   Packet Header   |
+-------------------+
|                   |
|   Data Buffer     |
|                   |
+-------------------+

The packet header contains a sequence number, acknowledgement information, and the size of the data buffer. The Data Buffer contains user payload data, which is opaque to Radon.

Packet Header

The header is a 16-byte buffer with the following format:

+----------------+
| magic | stream |
+----------------+
|      seq       |
+----------------+
|      ack       |
+----------------+
|    history     |
+----------------+
|                |
|    payload     |
|                |
+----------------+

Bytes       Field                       Header Variable
0-1         16-bit Magic Number         magic
2-3         16-bit Stream Identifier    stream
4-7         32-bit Sequence Number      seq
8-11        32-bit ACK Number           ack
12-15       32-bit ACK History          history
16-?        Variable-length payload

The size of the header is fixed at 16 bytes. The rest of the payload is considered to be part of the buffer. As such, the size of the payload returned to the caller upon receipt is simply the size of the UDP packet's payload minus the size of the header.

Each packet contains a sliding window containing the acknowledgement history for the last 32 packets. Each bit in the history buffer represents a sequence number. The bit is '0' if no packet with that sequence number has been received, and is '1' if a packet with that sequence number has been received.

The least-significant bit contains represents the sequence number contained in the ack field, and each subsequent higher-order bit represents the subsequent sequence number.

The header format is almost unmodified from this Gaffer on Games post.

Sender Protocol

When a packet is sent, the sender generates a unique sequence number for the packet and sends it to the receiver. Upon doing so, the sender adds the packet to a retransmission buffer. Each item in the retransmit buffer has the following fields:

  • The packet data to resend if lost
  • The remaining timeout until resend is needed
  • The number of times the packet has been sent

The caller is responsible for calling RnSocket::update() once per frame of the simulation. In update(), Radon goes through each item in the retransmission buffer and decrements its remaining timeout until retransmission.

If, upon decrementing, the timeout is zero or negative, Radon resends the packet and increments the number of times the packet has been sent. If the packet has now been sent the maximun number of retries, Radon removes the packet from the retransmission buffer and forgets about it. Otherwise, Radon resets the packet's timeout to the full timeout value.

When the sender receives a packet which acknowledges a packet in the retransmission buffer, it removes that packet from the retransmission buffer and forgets about it.

Receiver Protocol

The socket tracks its own copies the ack and history fields from the packet header format. When a packet is received, its sequence number is inspected.

If the sequence number comes before the sliding window, Radon drops the packet due to its age. Otherwise, Radon checks the ack history to determine if the packet has already been received. If it has been received, Radon drops the packet as a duplicate. If the packet has not been received, Radon records its receipt in the ACK history (shifting the history forward far enough to contain the new sequence number, if necessary), and delivers the packet to the caller.

Sequence Number Wraparound

Radon uses a 32-bit unsigned integer as its packet sequence number. At ths time, Random explicitly does not handle wraparound, as the target application (networked games / simulations) rarely run long enough to run into wraparound problems.

For example, if a game sends 30 packets per second (which is a little higher than one would naturally expect), the sequence number would wrap around in:

    2^32        Sequence numbers
/     30        Sequence numbers used per second
/     60        Seconds per minute
/     60        Minutes per hour
/     24        Hours per day
/    365        Days per year

=   ~4.5        Years before a sequence number wraparound

About

Reliable, one-time delivery UDP for C++

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published