Expand description
FLNet - sending data over WebRTC in libc and wasm
FLNet is used in fledger to communicate between browsers. It uses the WebRTC protocol and a signalling-server with websockets. The outstanding feature of this implementation is that it works both for WASM and libc with the same interface. This allows to write the core code once, and then re-use it for both WASM and libc.
WebRTC in short
WebRTC is an amazing technique that has been invented to let browsers exchange data without a third party server. There are many protocols involved in making this happen, and the first use-case of WebRTC was to share video and audio directly between two browsers. But WebRTC also allows for a ‘data’ channel where you can send any data you want.
For WebRTC to work, the following is needed:
- a WebRTC implementation on both ends: WebRTC exists for browsers, node, and since the end of 2021 also as a rust-crate
- a signalling server for the initial setup: as most browsers are behind a router, it is not possible to connect directly without some help. The signalling server is needed to exchange the information for setting up the communication
- a STUN server: for WebRTC to work also behind a NAT, somebody needs to tell the browser which address it has. The STUN server does amazing things like poking holes through firewalls, but this doesn’t always work, this is why you need an optional
- a TURN server: if WebRTC doesn’t manage to connect two WebRTC endpoints with each other, the last resort is using a TURN server. This is the old-school way of connecting two endpoints using a server that forwards traffic in both directions
Here is a
docker-compose.signalling.yaml
ready to run the flsignal
and coturn.
For a more in-depth presentation, including some pitfalls, I liked this article: What is WebRTC and how to avoid its 3 deadliest pitfalls
Example client/server
Here is a short example of how two nodes can communicate with each other. First of all you need a locally runing signalling server that you can spin up with:
cd cli; cargo run --bin flsignal -- -vv
For more debugging, it is sometimes useful to add -vvv
, or even -vvvv
, but then
it gets very verbose!
Here is a small example of how to setup a server that listens to messages from clients.
// The server waits for connections by the clients and prints the messages received.
async fn server() -> Result<(), BrokerError> {
// Create a random node-configuration. It uses serde for easy serialization.
let nc = flnet::config::NodeConfig::new();
// Connect to the signalling server and wait for connection requests.
let mut net = flnet::network_start(nc.clone(), "ws://localhost:8765")
.await
.expect("Starting network");
// Print our ID so it can be copied to the server
log::info!("Our ID is: {:?}", nc.info.get_id());
loop {
// Wait for messages and print them to stdout.
log::info!("Got message: {:?}", net.recv().await);
}
}
It is contacted by the client node, which needs the ID of the server to know where to connect to:
// A client sends a single message to a server and then disconnects.
async fn client(server_id: &str) -> Result<(), BrokerError> {
// Create a random node-configuration. It uses serde for easy serialization.
let nc = flnet::config::NodeConfig::new();
// Connect to the signalling server and wait for connection requests.
let mut net = flnet::network_start(nc.clone(), "ws://localhost:8765")
.await
.expect("Starting network");
// Need to get the client-id from the message in client()
let server_id = U256::from_str(server_id).expect("get client id");
log::info!("Server id is {server_id:?}");
// This sends the message by setting up a connection using the signalling server.
// The client must already be running and be registered with the signalling server.
// Using `SendNodeMessage` will set up a connection using the signalling server, but
// in the best case, the signalling server will not be used anymore afterwards.
net.send_msg(server_id, "ping".into())?;
// Wait for the connection to be set up and the message to be sent.
wait_ms(1000).await;
Ok(())
}
You can find this code and more examples in the examples directory here: https://github.com/ineiti/fledger/tree/0.7.0/examples Other examples include a full example with a shared code, a libc and a wasm implementation: Ping-Pong.
Features
FLNet has two features to choose between WASM and libc implementation.
Unfortunately it is not detected automatically yet.
If you write code that is shared between the two, you can add
flnet
without any features to your Cargo.toml
.
The appropriate feature will then be added once you compile the
final code.
The wasm
feature enables the WASM backend, which only
includes the WebRTC connections and the signalling client:
flnet = {features = ["wasm"], version = "0.7"}
With the libc
feature, the libc backend is enabled,
which has the WebRTC connections, and both the signalling client
and server part:
flnet = {features = ["libc"], version = "0.7"}
Cross-platform usage
If you build a software that will run both in WASM and with libc, one way to do so is to use trunk, which allows you to write the wasm part very similar to the libc part.
In fledger, this is accomplished with the following structure:
flbrowser fledger-cli
\ /
shared
|
flnet
The shared
code only depends on the flnet
crate without a given feature.
It is common between the WASM and the libc implementation.
Only the flbrowser
and fledger-cli
depend on the flnet
crate with the
appropriate feature set.
You can find an example in the ping-pong directory.
libc
The libc code is made possible by the excellent webrtc library.
The libc
feature enables both the WebSocket-server and the WebRTC for libc-systems.
wasm
This feature implements the WebSocket
and WebRTC
trait for the wasm
platform.
It can be used for the browser or for node.
Modules
Structs
Enums
Functions
broker::Broker<NetworkMessage>
with a given node
- and connection
-configuration.
This returns a raw broker which is mostly suited to connect to other brokers.
If you need an easier access to the WebRTC network, use network_start
, which returns
a structure with a more user-friendly API.node
-
and connection
-configuration.
It returns a user-friendly API that can be used to send and
receive messages.
If you want to connect the network with other brokers, then use
the network_broker_start
method.