ENet Reliable UDP networking library: <http://enet.bespin.org/>
a C# wrapper, for comparison: <https://github.com/nxrighthere/ENet-CSharp>
benchmark of ENet against some other UDP libraries:
<https://github.com/nxrighthere/BenchmarkNet/wiki/Benchmark-Results>
# Channels
A "client" host can communicate bi-directionally on as many channels as a
"server" host supports.
The following two scenarios demonstrate how the "server" of a new connection
always determines the number of channels actually used in the connection:
*Scenario 1*
- Host A has 2 channels
- Host B has 4 channels
> Host A requests to connect to host B with 6 channels.
- This returns a (not-connected) peer with `channelCount` of 6.
- When the connected event is received by A, the peer `channelCount`
will be set to 4.
- Host B receives the request to connect from host A and the connected A peer
will have 4 channels.
> A and B can communicate bi-directionally on 4 channels.
*Scenario 2*
- Host A has 4 channels
- Host B has 2 channels
> Host A requests to connect to host B with 6 channels.
- This returns a (not-connected) B peer with `channelCount` of 6.
- When the connected event is received by A, the B peer channelCount
will be set to 2.
- Host B receives the request to connect from host A the connected
A peer will have 2 channels.
> A and B can communicate bi-directionally on 2 channels.
# Peers
## Service events
- `Connect` -- a peer has completed the connection request initiated with
`connect()`; the host that initiated the connection needs to call `flush()` or
`service()` in order to send the acknowledgement to generate the corresponding
`Connect` event on the server end
- `Disconnect`
- `Receive`
## Sending Packets
- `int enet_peer_send (peer, channelID, packet)`:
No error is generated for a host sending to a peer where the
`channelID` is greater than the host `channelLimit`; however the
function *does* return an error if the `channelID` exceeds the
peer's `channelCount`
errors (return -1):
* `peer->channelCount <= channelID`
* `peer->state != CONNECTED`
* `peer->host.maximumPacketLength < packet.dataLength`
* either:
+ (internal variable) `fragmentLength < packet.dataLength`, and
either:
a. `ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT < fragmentCount`s
(internal variable)
or:
b. outgoing command fragment malloc failure
* or:
+ outgoing command malloc failure
The first 3 and (a.) should be detectable before calling the low-level send
function, leaving only malloc failure in which case no
`ENetOutgoingCommands` will be "queued" (no fragments) or "setup"
(fragments). I'm assuming at this point the packet will still be destroyed
automatically (???).
- `void enet_host_broadcast (host, channelID, packet)`:
Does not check host for host's `channelLimit < channelID`, but will
silently fail when calling `enet_peer_send` for each connected peer
where `peer.channelCount <= channelID`.
`enet_host_broadcast` iterates over each *allocated*
peer, that is, the peerCount requested when the host was created
but will check for connection status and not call send for
disconnected peers
# Design
Currently we have hosts and peers implemented.
Peers are a kind of 'weak' reference to the peer inside the host structure. When
a peer or host is dropped, no 'disconnect' messages are sent; eventually the
remaining server or client will generate a '`Disconnect`' event (about 10
seconds for a client, about 20 seconds for a server).
Outgoing messages can be queued explicitly, e.g. calling `send()` on a peer to
send a packet, or can be queued implicitly, e.g. when client receives a
`Connect` event a connection acknowledgement will be queued.
Queued messages are only sent by calling `service()` or `flush()`.
*Q*: should any additional management of connections be attempted?
Currently `Peer` structures are returned by `Connect`, `Receive`, and
`Disconnect` events. In the case of a `Receive` event, this peer should *not* be
dropped.
The cases are:
- When `connect()` is called, the returned peer is in the `Connecting` state
- When a `Peer` is received in a `Connect` event, the peer will be in
`Connected` state
- When a `Peer` is received in a `Receive` event, the peer *should* (???) be
in a `Connected` state; i.e. it should only weakly *refer* to a peer that
had previously connected
- When a `Peer` is received in a `Disconnect` event, the peer returned is
in `Disconnected` state
Idea: interact only with *peer IDs*. Internally this would require translating
a pointer to the peer into an index into the peer array.
Issues:
- How to tell if a peer ID has been re-used and now represents a *different*
peer?
* One idea would be to combine the peer index with a generation number.
Each time a `Connect` event is received that refers to a peer, the
generation of that peer index is incremented and will no longer send any
messages that do not match the new peer generation.
- What if a peer ID is presented for an operation, e.g. `send()`, which refers
to a disconnected peer?
* If the above generational approach is taken then there is the additional
possibility that a peer ID with a different generation is given to the
`send()` command. In either case it should result in some kind of
failure, possibly a different error in each case.
For a client, generations may or may not be needed in cases such as a single
peer that is a server. This would, however, prevent sending messages that were
intended for a prior session, i.e. if the client had disconnected and
reconnected.
Note: in the end it might be better not to enforce any kind of policy here. A
peer is simply a weak reference and it is up to the user to check the state
before trying operations.