2018-01-25 05:02:26 +00:00
|
|
|
# P2P Multiplex Connection
|
2017-12-31 17:07:08 -05:00
|
|
|
|
2018-01-09 12:44:49 -05:00
|
|
|
## MConnection
|
2017-12-31 17:07:08 -05:00
|
|
|
|
2018-01-09 12:44:49 -05:00
|
|
|
`MConnection` is a multiplex connection that supports multiple independent streams
|
|
|
|
with distinct quality of service guarantees atop a single TCP connection.
|
2018-01-25 05:02:26 +00:00
|
|
|
Each stream is known as a `Channel` and each `Channel` has a globally unique *byte id*.
|
2018-01-09 12:44:49 -05:00
|
|
|
Each `Channel` also has a relative priority that determines the quality of service
|
2018-01-25 05:02:26 +00:00
|
|
|
of the `Channel` compared to other `Channel`s.
|
|
|
|
The *byte id* and the relative priorities of each `Channel` are configured upon
|
2017-12-31 17:07:08 -05:00
|
|
|
initialization of the connection.
|
|
|
|
|
2018-01-25 05:02:26 +00:00
|
|
|
The `MConnection` supports three packet types:
|
|
|
|
|
|
|
|
- Ping
|
|
|
|
- Pong
|
|
|
|
- Msg
|
2017-12-31 17:07:08 -05:00
|
|
|
|
|
|
|
### Ping and Pong
|
|
|
|
|
2018-01-09 12:44:49 -05:00
|
|
|
The ping and pong messages consist of writing a single byte to the connection; 0x1 and 0x2, respectively.
|
2017-12-31 17:07:08 -05:00
|
|
|
|
2018-01-25 05:02:26 +00:00
|
|
|
When we haven't received any messages on an `MConnection` in time `pingTimeout`, we send a ping message.
|
2018-01-09 12:44:49 -05:00
|
|
|
When a ping is received on the `MConnection`, a pong is sent in response only if there are no other messages
|
2018-01-26 17:26:11 -05:00
|
|
|
to send and the peer has not sent us too many pings (TODO).
|
2017-12-31 17:07:08 -05:00
|
|
|
|
2018-01-25 05:02:26 +00:00
|
|
|
If a pong or message is not received in sufficient time after a ping, the peer is disconnected from.
|
2017-12-31 17:07:08 -05:00
|
|
|
|
|
|
|
### Msg
|
|
|
|
|
2018-01-25 05:02:26 +00:00
|
|
|
Messages in channels are chopped into smaller `msgPacket`s for multiplexing.
|
2017-12-31 17:07:08 -05:00
|
|
|
|
|
|
|
```
|
|
|
|
type msgPacket struct {
|
|
|
|
ChannelID byte
|
|
|
|
EOF byte // 1 means message ends here.
|
|
|
|
Bytes []byte
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2018-01-25 05:02:26 +00:00
|
|
|
The `msgPacket` is serialized using [go-wire](https://github.com/tendermint/go-wire) and prefixed with 0x3.
|
2017-12-31 17:07:08 -05:00
|
|
|
The received `Bytes` of a sequential set of packets are appended together
|
2018-01-25 05:02:26 +00:00
|
|
|
until a packet with `EOF=1` is received, then the complete serialized message
|
|
|
|
is returned for processing by the `onReceive` function of the corresponding channel.
|
2017-12-31 17:07:08 -05:00
|
|
|
|
|
|
|
### Multiplexing
|
|
|
|
|
2018-01-25 05:02:26 +00:00
|
|
|
Messages are sent from a single `sendRoutine`, which loops over a select statement and results in the sending
|
2017-12-31 17:07:08 -05:00
|
|
|
of a ping, a pong, or a batch of data messages. The batch of data messages may include messages from multiple channels.
|
|
|
|
Message bytes are queued for sending in their respective channel, with each channel holding one unsent message at a time.
|
2018-01-19 17:10:08 -05:00
|
|
|
Messages are chosen for a batch one at a time from the channel with the lowest ratio of recently sent bytes to channel priority.
|
2017-12-31 17:07:08 -05:00
|
|
|
|
|
|
|
## Sending Messages
|
|
|
|
|
|
|
|
There are two methods for sending messages:
|
|
|
|
```go
|
|
|
|
func (m MConnection) Send(chID byte, msg interface{}) bool {}
|
|
|
|
func (m MConnection) TrySend(chID byte, msg interface{}) bool {}
|
|
|
|
```
|
|
|
|
|
|
|
|
`Send(chID, msg)` is a blocking call that waits until `msg` is successfully queued
|
|
|
|
for the channel with the given id byte `chID`. The message `msg` is serialized
|
|
|
|
using the `tendermint/wire` submodule's `WriteBinary()` reflection routine.
|
|
|
|
|
2018-01-09 12:44:49 -05:00
|
|
|
`TrySend(chID, msg)` is a nonblocking call that queues the message msg in the channel
|
|
|
|
with the given id byte chID if the queue is not full; otherwise it returns false immediately.
|
2017-12-31 17:07:08 -05:00
|
|
|
|
|
|
|
`Send()` and `TrySend()` are also exposed for each `Peer`.
|
|
|
|
|
|
|
|
## Peer
|
|
|
|
|
|
|
|
Each peer has one `MConnection` instance, and includes other information such as whether the connection
|
|
|
|
was outbound, whether the connection should be recreated if it closes, various identity information about the node,
|
|
|
|
and other higher level thread-safe data used by the reactors.
|
|
|
|
|
|
|
|
## Switch/Reactor
|
|
|
|
|
|
|
|
The `Switch` handles peer connections and exposes an API to receive incoming messages
|
|
|
|
on `Reactors`. Each `Reactor` is responsible for handling incoming messages of one
|
|
|
|
or more `Channels`. So while sending outgoing messages is typically performed on the peer,
|
|
|
|
incoming messages are received on the reactor.
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Declare a MyReactor reactor that handles messages on MyChannelID.
|
|
|
|
type MyReactor struct{}
|
|
|
|
|
|
|
|
func (reactor MyReactor) GetChannels() []*ChannelDescriptor {
|
|
|
|
return []*ChannelDescriptor{ChannelDescriptor{ID:MyChannelID, Priority: 1}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (reactor MyReactor) Receive(chID byte, peer *Peer, msgBytes []byte) {
|
|
|
|
r, n, err := bytes.NewBuffer(msgBytes), new(int64), new(error)
|
|
|
|
msgString := ReadString(r, n, err)
|
|
|
|
fmt.Println(msgString)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Other Reactor methods omitted for brevity
|
|
|
|
...
|
|
|
|
|
|
|
|
switch := NewSwitch([]Reactor{MyReactor{}})
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
// Send a random message to all outbound connections
|
|
|
|
for _, peer := range switch.Peers().List() {
|
|
|
|
if peer.IsOutbound() {
|
|
|
|
peer.Send(MyChannelID, "Here's a random message")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|