├── .gitignore ├── docs ├── images │ └── message-io-title.png ├── basic_concepts.md └── performance_benchmarks.md ├── src ├── util.rs ├── adapters.rs ├── lib.rs ├── adapters │ ├── template.rs │ ├── framed_tcp.rs │ └── tcp.rs ├── network │ ├── registry.rs │ ├── loader.rs │ ├── endpoint.rs │ ├── poll.rs │ ├── remote_addr.rs │ ├── resource_id.rs │ ├── transport.rs │ ├── adapter.rs │ └── driver.rs ├── util │ ├── thread.rs │ └── encoding.rs └── events.rs ├── .rustfmt.toml ├── examples ├── multicast │ ├── README.md │ └── main.rs ├── ping-pong │ ├── common.rs │ ├── README.md │ ├── main.rs │ ├── client.rs │ └── server.rs ├── file-transfer │ ├── main.rs │ ├── README.md │ ├── common.rs │ ├── receiver.rs │ └── sender.rs ├── distributed │ ├── common.rs │ ├── main.rs │ ├── README.md │ ├── discovery_server.rs │ └── participant.rs └── throughput │ ├── README.md │ └── main.rs ├── Cargo.toml ├── .github └── workflows │ └── rust.yml ├── benches └── latency.rs ├── LICENSE ├── README.md └── tests └── integration.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /docs/images/message-io-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lemunozm/message-io/HEAD/docs/images/message-io-title.png -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | /// Utility for created nested named threads 2 | pub mod thread; 3 | 4 | /// Frame encoding to convert a data stream into packets. 5 | /// It can be used as a utility to build adapters. 6 | pub mod encoding; 7 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | control_brace_style = "ClosingNextLine" 2 | use_small_heuristics = "Max" 3 | reorder_imports = false 4 | reorder_modules = false 5 | trailing_semicolon = false 6 | use_field_init_shorthand = true 7 | use_try_shorthand = true 8 | where_single_line = true 9 | -------------------------------------------------------------------------------- /src/adapters.rs: -------------------------------------------------------------------------------- 1 | mod template; 2 | 3 | #[cfg(feature = "tcp")] 4 | pub mod tcp; 5 | #[cfg(feature = "tcp")] 6 | pub mod framed_tcp; 7 | #[cfg(feature = "udp")] 8 | pub mod udp; 9 | #[cfg(feature = "websocket")] 10 | pub mod ws; 11 | // Add new adapters here 12 | // ... 13 | -------------------------------------------------------------------------------- /examples/multicast/README.md: -------------------------------------------------------------------------------- 1 | # Multicast example 2 | This example allows to create entities that can find among them using multicast. 3 | 4 | ## Test it! 5 | In several terminals or machines connected to the same LAN, run the multicast example: 6 | ``` 7 | cargo run --example multicast 8 | ``` 9 | 10 | Consecutive nodes will find each others. 11 | -------------------------------------------------------------------------------- /examples/ping-pong/common.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub enum FromClientMessage { 5 | Ping, 6 | } 7 | 8 | #[derive(Serialize, Deserialize)] 9 | pub enum FromServerMessage { 10 | Pong(usize), // Used for connection oriented protocols 11 | UnknownPong, // Used for non-connection oriented protocols 12 | } 13 | -------------------------------------------------------------------------------- /examples/file-transfer/main.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod sender; 3 | mod receiver; 4 | 5 | pub fn main() { 6 | let args: Vec = std::env::args().collect(); 7 | match args.get(1).unwrap_or(&String::new()).as_ref() { 8 | "send" => match args.get(2) { 9 | Some(file_path) => sender::run(file_path.into()), 10 | None => println!("The sender needs a 'file path'"), 11 | }, 12 | "recv" => receiver::run(), 13 | _ => println!("Usage: recv | (send )"), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/distributed/common.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | use std::net::{SocketAddr}; 4 | 5 | #[derive(Serialize, Deserialize)] 6 | 7 | pub enum Message { 8 | // To DiscoveryServer 9 | RegisterParticipant(String, SocketAddr), 10 | UnregisterParticipant(String), 11 | 12 | // From DiscoveryServer 13 | ParticipantList(Vec<(String, SocketAddr)>), 14 | ParticipantNotificationAdded(String, SocketAddr), 15 | ParticipantNotificationRemoved(String), 16 | 17 | // From Participant to Participant 18 | Greetings(String, String), //name and greetings 19 | } 20 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Check the [Github README](https://github.com/lemunozm/message-io), 2 | //! to see an overview of the library. 3 | 4 | #[cfg(doctest)] 5 | // Tells rustdoc where is the README to compile and test the rust code found there 6 | doc_comment::doctest!("../README.md"); 7 | 8 | /// Adapter related information. 9 | /// If some adapter has special values or configuration, it is specified here. 10 | pub mod adapters; 11 | 12 | /// Main API. Create connections, send and receive message, signals,... 13 | pub mod node; 14 | 15 | /// It contains all the resources and tools to create and manage connections. 16 | pub mod network; 17 | 18 | /// A set of utilities to deal with asynchronous events. 19 | /// This module offers a synchronized event queue and timed events. 20 | pub mod events; 21 | 22 | /// General purpose utilities. 23 | pub mod util; 24 | -------------------------------------------------------------------------------- /examples/ping-pong/README.md: -------------------------------------------------------------------------------- 1 | # Ping-Pong example 2 | 3 | This example shows a *clients-server* connection. 4 | The clients send a message every second and the server responds to it. 5 | 6 | You can run both client and server by several transports: `tcp`, `udp`, `ws` (*WebSocket*). 7 | 8 | ## Test it! 9 | 10 | Launch the server in a terminal: 11 | ``` 12 | cargo run --example ping-pong server 13 | ``` 14 | 15 | Run a client (one client per terminal): 16 | ``` 17 | cargo run --example ping-pong client (: | ) 18 | ``` 19 | You can play the disconnections and reconnections using `ctrl-c` over the clients. 20 | 21 | *Note: for *WebSocket* (`ws`), you can specify the address as usually `:` or as an url: 22 | `ws://domain:port/path` for normal websocket or `wss://domain:port/part` for secure web sockets. 23 | 24 | -------------------------------------------------------------------------------- /examples/file-transfer/README.md: -------------------------------------------------------------------------------- 1 | # TCP client and server example 2 | This example allows to send and receive files through TCP. 3 | 4 | ## Test it! 5 | First, choose a file to send it. If it's big, better! 6 | If you are in linux, you can create a 1GB file with the following command: 7 | `truncate -s 1G ` 8 | 9 | Launch the receiver in a terminal. 10 | It acts as a server, being able to receive files from several clients at the same time. 11 | ``` 12 | cargo run --example file-transfer recv 13 | ``` 14 | 15 | Run a sender with a file path (one sender per terminal): 16 | ``` 17 | cargo run --example file-transfer send 18 | ``` 19 | 20 | Note: You can play the with disconnections using `ctrl-c` over the sender/receiver. 21 | 22 | ## Desing notes 23 | The file is sent in chunks to not block the `EventQueue` if the transfer is really long. 24 | 25 | -------------------------------------------------------------------------------- /examples/distributed/main.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod discovery_server; 3 | mod participant; 4 | 5 | pub fn main() { 6 | let args: Vec = std::env::args().collect(); 7 | 8 | match args.get(1).unwrap_or(&String::new()).as_ref() { 9 | "discovery-server" => match discovery_server::DiscoveryServer::new() { 10 | Ok(discovery_server) => discovery_server.run(), 11 | Err(err) => println!("Can not run the discovery server: {}", err), 12 | }, 13 | "participant" => match args.get(2) { 14 | Some(name) => match participant::Participant::new(name) { 15 | Ok(participant) => participant.run(), 16 | Err(err) => println!("Can not run the participant: {}", err), 17 | }, 18 | None => println!("The participant needs a 'name'"), 19 | }, 20 | _ => println!("Usage: discovery-server | participant "), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/throughput/README.md: -------------------------------------------------------------------------------- 1 | # Throughput example 2 | 3 | This example shows the throughput among differents transport in both ways: used by `message-io` and used isolated from the library, natively. 4 | 5 | The aim is to compare two dimensions: different procols among us and the overhead `message-io` adds. 6 | 7 | To run this tests, run: 8 | ```sh 9 | cargo run --release --example throughput 10 | ``` 11 | 12 | **NOT FORGET** to run this example with the `--release` flag to get the real measurements. 13 | 14 | The throughput is measured by sending *1GB* of data between two connected endpoints by **localhost**. 15 | The measure starts when the sender starts sending the data and finished when the receiver receives 16 | the entire data. 17 | 18 |

19 | 20 |

21 | 22 | To know more about `message-io` performance and how to interpret the results, 23 | see the [performance](../../docs/performance_benchmarks.md) document. 24 | -------------------------------------------------------------------------------- /examples/distributed/README.md: -------------------------------------------------------------------------------- 1 | # Distributed example 2 | This example allows to create a distributed network of participants based in a discovery server. 3 | 4 | ## Test it! 5 | Launch the discovery server in a terminal: 6 | ``` 7 | cargo run --example distributed discovery-server 8 | ``` 9 | 10 | Run a participant (one terminal per participant): 11 | ``` 12 | cargo run --example distributed participant 13 | cargo run --example distributed participant 14 | ... 15 | cargo run --example distributed participant 16 | ``` 17 | 18 | Note: You can play closing the participants (ctrl-c) in order to see the removed notifications. 19 | 20 | ## Network topology 21 | The participant register and unregister itself in the discovery server, that will notify the rest of the participant about the new existance member. 22 | Then, a participant can create a direct and private communication to another participant. 23 | 24 |

25 | 26 |

27 | 28 | -------------------------------------------------------------------------------- /examples/multicast/main.rs: -------------------------------------------------------------------------------- 1 | use message_io::network::{NetEvent, Transport}; 2 | use message_io::node::{self}; 3 | 4 | fn main() { 5 | let args: Vec = std::env::args().collect(); 6 | let my_name = match args.get(1) { 7 | Some(name) => name, 8 | None => return println!("Please, choose a name"), 9 | }; 10 | 11 | let (handler, listener) = node::split::<()>(); 12 | 13 | let multicast_addr = "239.255.0.1:3010"; 14 | let (endpoint, _) = handler.network().connect(Transport::Udp, multicast_addr).unwrap(); 15 | 16 | listener.for_each(move |event| match event.network() { 17 | NetEvent::Connected(_, _always_true_for_udp) => { 18 | println!("Notifying on the network"); 19 | handler.network().send(endpoint, my_name.as_bytes()); 20 | 21 | // Since the address belongs to the multicast range (from 224.0.0.0 to 239.255.255.255) 22 | // the internal resource will be configured to receive multicast messages. 23 | handler.network().listen(Transport::Udp, multicast_addr).unwrap(); 24 | } 25 | NetEvent::Accepted(_, _) => unreachable!(), // UDP is not connection-oriented 26 | NetEvent::Message(_, data) => { 27 | println!("{} greets to the network!", String::from_utf8_lossy(&data)); 28 | } 29 | NetEvent::Disconnected(_) => (), 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /examples/ping-pong/main.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod client; 3 | mod server; 4 | 5 | use message_io::network::{Transport, ToRemoteAddr}; 6 | 7 | use std::net::{ToSocketAddrs}; 8 | 9 | const HELP_MSG: &str = concat!( 10 | "Usage: ping-pong server (tcp | udp | ws) \n", 11 | " pong-pong client (tcp | udp | ws) (: | url)" 12 | ); 13 | 14 | pub fn main() { 15 | let args: Vec = std::env::args().collect(); 16 | 17 | let transport = match args.get(2).unwrap_or(&"".into()).as_ref() { 18 | "tcp" => Transport::FramedTcp, // The non-streamed version of tcp. 19 | "udp" => Transport::Udp, 20 | "ws" => Transport::Ws, 21 | _ => return println!("{}", HELP_MSG), 22 | }; 23 | 24 | match args.get(1).unwrap_or(&"".into()).as_ref() { 25 | "client" => match args.get(3) { 26 | Some(remote_addr) => { 27 | let remote_addr = remote_addr.to_remote_addr().unwrap(); 28 | client::run(transport, remote_addr); 29 | } 30 | None => return println!("{}", HELP_MSG), 31 | }, 32 | "server" => { 33 | match args.get(3).unwrap_or(&"".into()).parse() { 34 | Ok(port) => { 35 | let addr = ("0.0.0.0", port).to_socket_addrs().unwrap().next().unwrap(); 36 | server::run(transport, addr); 37 | } 38 | Err(_) => return println!("{}", HELP_MSG), 39 | }; 40 | } 41 | _ => return println!("{}", HELP_MSG), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/basic_concepts.md: -------------------------------------------------------------------------------- 1 | # Basic concepts 2 | The main `message-io` entity is the **node**. 3 | It is in charge of managing the different connections, performing actions over them and offering 4 | the events comming from the network or by the own node (called signals). 5 | 6 |

7 | 8 |

9 | 10 | It is splitted in two sides: 11 | - `NodeHandler`: the "input" side, that performs actions. 12 | Theses actions can be network actions, send signals, or stop the `NodeListener`. 13 | This entity is clonable and sharable among threads, so you can send messages/signals anywhere from 14 | your program. 15 | - `NodeListener`: the "output" side, that receives events synchronized from one callback. 16 | 17 | And each side has two main entities: 18 | - A synchronized queue (`EventSender` and `EventReceiver`) to send and receive signals from 19 | the own node. 20 | This is useful to make sending rates, messages based on timeouts, etc. 21 | - The network that is splited in a `NetworkController` to perform actions, and a `NetworkProcessor` 22 | to receive events from the network. 23 | The actions could be create any number of connections/listener, remove them or sending messages. 24 | The connections are managed by the network and identified by an `Endpoint`, that is a few copiable/hashable struct that represent those connections uniquely. 25 | It can be understood as the remitter/recipient of the message. 26 | -------------------------------------------------------------------------------- /src/adapters/template.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | use crate::network::adapter::{ 4 | Resource, Remote, Local, Adapter, SendStatus, AcceptedType, ReadStatus, ConnectionInfo, 5 | ListeningInfo, PendingStatus, 6 | }; 7 | use crate::network::{RemoteAddr, Readiness, TransportConnect, TransportListen}; 8 | 9 | use mio::event::{Source}; 10 | 11 | use std::net::{SocketAddr}; 12 | use std::io::{self}; 13 | 14 | pub(crate) struct MyAdapter; 15 | impl Adapter for MyAdapter { 16 | type Remote = RemoteResource; 17 | type Local = LocalResource; 18 | } 19 | 20 | pub(crate) struct RemoteResource; 21 | impl Resource for RemoteResource { 22 | fn source(&mut self) -> &mut dyn Source { 23 | todo!() 24 | } 25 | } 26 | 27 | impl Remote for RemoteResource { 28 | fn connect_with( 29 | config: TransportConnect, 30 | remote_addr: RemoteAddr, 31 | ) -> io::Result> { 32 | todo!() 33 | } 34 | 35 | fn receive(&self, process_data: impl FnMut(&[u8])) -> ReadStatus { 36 | todo!() 37 | } 38 | 39 | fn send(&self, data: &[u8]) -> SendStatus { 40 | todo!() 41 | } 42 | 43 | fn pending(&self, _readiness: Readiness) -> PendingStatus { 44 | todo!() 45 | } 46 | } 47 | 48 | pub(crate) struct LocalResource; 49 | impl Resource for LocalResource { 50 | fn source(&mut self) -> &mut dyn Source { 51 | todo!() 52 | } 53 | } 54 | 55 | impl Local for LocalResource { 56 | type Remote = RemoteResource; 57 | 58 | fn listen_with(config: TransportListen, addr: SocketAddr) -> io::Result> { 59 | todo!() 60 | } 61 | 62 | fn accept(&self, accept_remote: impl FnMut(AcceptedType<'_, Self::Remote>)) { 63 | todo!() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "message-io" 3 | version = "0.19.0" 4 | authors = ["lemunozm "] 5 | edition = "2018" 6 | readme = "README.md" 7 | license = "Apache-2.0" 8 | description = "Fast and easy-to-use event-driven network library" 9 | homepage = "https://github.com/lemunozm/message-io/" 10 | repository = "https://github.com/lemunozm/message-io/" 11 | keywords = ["network", "message", "events", "non-blocking", "tcp"] 12 | categories = ["asynchronous", "game-development", "network-programming", "rust-patterns", "web-programming::websocket"] 13 | 14 | [badges] 15 | maintenance = { status = "actively-developed" } 16 | 17 | [package.metadata.docs.rs] 18 | all-features = true 19 | 20 | [features] 21 | default = ["tcp", "udp", "websocket"] # All features by default 22 | tcp = ["mio/net", "socket2"] 23 | udp = ["mio/net", "socket2"] 24 | websocket = ["tungstenite", "url", "tcp"] 25 | 26 | [dependencies] 27 | mio = { version = "0.8", features = ["os-poll"] } 28 | serde = { version = "1.0", features = ["derive"] } 29 | crossbeam-channel = "0.5" 30 | crossbeam-utils = "0.8" 31 | log = "0.4" 32 | strum = { version = "0.24", features = ["derive"] } 33 | socket2 = { version = "0.5.1", features = ["all"], optional = true} 34 | tungstenite = { version = "0.26", features = ["url"], optional = true } 35 | url = { version = "2.2", optional = true } 36 | integer-encoding = "3.0.2" 37 | lazy_static = "1.4.0" 38 | 39 | [target.'cfg(target_os = "linux")'.dependencies.nix] 40 | version = "0.26.2" 41 | default-features = false 42 | features = ["socket", "uio", "net"] 43 | 44 | [target.'cfg(unix)'.dependencies.libc] 45 | version = "0.2.137" 46 | default-features = false 47 | 48 | [dev-dependencies] 49 | bincode = "1.3.1" 50 | criterion = "0.4" 51 | fern = "0.6.0" 52 | chrono = { version = "0.4.24", features = ["clock"], default-features = false } 53 | test-case = "1.1.0" 54 | rand = "0.9" 55 | httparse = "1.3.5" 56 | doc-comment = "0.3" 57 | 58 | [[bench]] 59 | name = "latency" 60 | harness = false 61 | -------------------------------------------------------------------------------- /examples/file-transfer/common.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | #[derive(Serialize, Deserialize)] 4 | pub enum SenderMsg { 5 | //From sender to receiver 6 | FileRequest(String, usize), // name, size 7 | Chunk(Vec), // data 8 | } 9 | 10 | #[derive(Serialize, Deserialize)] 11 | pub enum ReceiverMsg { 12 | //From receiver to sender 13 | CanReceive(bool), 14 | } 15 | 16 | /* 17 | ┌──────┐ ┌────────┐ 18 | │Sender│ │Receiver│ 19 | └──┬───┘ └───┬────┘ 20 | │ FileRequest │ 21 | │ ──────────────────────────────────────────────────────> 22 | │ │ 23 | │ CanReceive (if the server can receive the file or not)│ 24 | │ <────────────────────────────────────────────────────── 25 | │ │ 26 | │ Chunk │ 27 | │ ──────────────────────────────────────────────────────> 28 | │ │ 29 | . . 30 | . . 31 | . . 32 | . . 33 | │ Chunk │ 34 | │ ──────────────────────────────────────────────────────> 35 | │ │ 36 | │ Chunk │ 37 | │ ──────────────────────────────────────────────────────> 38 | │ │ 39 | │ Chunk │ 40 | │ ──────────────────────────────────────────────────────> 41 | ┌──┴───┐ ┌───┴────┐ 42 | │Sender│ │Receiver│ 43 | └──────┘ └────────┘ 44 | */ 45 | -------------------------------------------------------------------------------- /examples/ping-pong/client.rs: -------------------------------------------------------------------------------- 1 | use super::common::{FromServerMessage, FromClientMessage}; 2 | 3 | use message_io::network::{NetEvent, Transport, RemoteAddr}; 4 | use message_io::node::{self, NodeEvent}; 5 | 6 | use std::time::{Duration}; 7 | 8 | enum Signal { 9 | Greet, // This is a self event called every second. 10 | // Other signals here, 11 | } 12 | 13 | pub fn run(transport: Transport, remote_addr: RemoteAddr) { 14 | let (handler, listener) = node::split(); 15 | 16 | let (server_id, local_addr) = 17 | handler.network().connect(transport, remote_addr.clone()).unwrap(); 18 | 19 | listener.for_each(move |event| match event { 20 | NodeEvent::Network(net_event) => match net_event { 21 | NetEvent::Connected(_, established) => { 22 | if established { 23 | println!("Connected to server at {} by {}", server_id.addr(), transport); 24 | println!("Client identified by local port: {}", local_addr.port()); 25 | handler.signals().send(Signal::Greet); 26 | } 27 | else { 28 | println!("Can not connect to server at {} by {}", remote_addr, transport) 29 | } 30 | } 31 | NetEvent::Accepted(_, _) => unreachable!(), // Only generated when a listener accepts 32 | NetEvent::Message(_, input_data) => { 33 | let message: FromServerMessage = bincode::deserialize(&input_data).unwrap(); 34 | match message { 35 | FromServerMessage::Pong(count) => { 36 | println!("Pong from server: {} times", count) 37 | } 38 | FromServerMessage::UnknownPong => println!("Pong from server"), 39 | } 40 | } 41 | NetEvent::Disconnected(_) => { 42 | println!("Server is disconnected"); 43 | handler.stop(); 44 | } 45 | }, 46 | NodeEvent::Signal(signal) => match signal { 47 | Signal::Greet => { 48 | let message = FromClientMessage::Ping; 49 | let output_data = bincode::serialize(&message).unwrap(); 50 | handler.network().send(server_id, &output_data); 51 | handler.signals().send_with_timer(Signal::Greet, Duration::from_secs(1)); 52 | } 53 | }, 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /src/network/registry.rs: -------------------------------------------------------------------------------- 1 | use super::resource_id::{ResourceId}; 2 | use super::poll::{PollRegistry}; 3 | use super::adapter::{Resource}; 4 | 5 | use crate::util::thread::{OTHER_THREAD_ERR}; 6 | 7 | use std::collections::{HashMap}; 8 | use std::sync::{Arc, RwLock}; 9 | 10 | pub struct Register { 11 | pub resource: S, 12 | pub properties: P, 13 | 14 | poll_registry: Arc, 15 | } 16 | 17 | impl Register { 18 | fn new(resource: S, properties: P, poll_registry: Arc) -> Self { 19 | Self { resource, properties, poll_registry } 20 | } 21 | } 22 | 23 | impl Drop for Register { 24 | fn drop(&mut self) { 25 | self.poll_registry.remove(self.resource.source()); 26 | } 27 | } 28 | 29 | pub struct ResourceRegistry { 30 | resources: RwLock>>>, 31 | poll_registry: Arc, 32 | } 33 | 34 | impl ResourceRegistry { 35 | pub fn new(poll_registry: PollRegistry) -> Self { 36 | ResourceRegistry { 37 | resources: RwLock::new(HashMap::new()), 38 | poll_registry: Arc::new(poll_registry), 39 | } 40 | } 41 | 42 | /// Add a resource into the registry. 43 | pub fn register(&self, mut resource: S, properties: P, write_readiness: bool) -> ResourceId { 44 | // The registry must be locked for the entire implementation to avoid the poll 45 | // to generate events over not yet registered resources. 46 | let mut registry = self.resources.write().expect(OTHER_THREAD_ERR); 47 | let id = self.poll_registry.add(resource.source(), write_readiness); 48 | let register = Register::new(resource, properties, self.poll_registry.clone()); 49 | registry.insert(id, Arc::new(register)); 50 | id 51 | } 52 | 53 | /// Remove a register from the registry. 54 | /// This function ensure that the register is removed from the registry, 55 | /// but not the destruction of the resource itself. 56 | /// Because the resource is shared, the destruction will be delayed until the last reference. 57 | pub fn deregister(&self, id: ResourceId) -> bool { 58 | self.resources.write().expect(OTHER_THREAD_ERR).remove(&id).is_some() 59 | } 60 | 61 | /// Returned a shared reference of the register. 62 | pub fn get(&self, id: ResourceId) -> Option>> { 63 | self.resources.read().expect(OTHER_THREAD_ERR).get(&id).cloned() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/ping-pong/server.rs: -------------------------------------------------------------------------------- 1 | use super::common::{FromServerMessage, FromClientMessage}; 2 | 3 | use message_io::network::{NetEvent, Transport, Endpoint}; 4 | use message_io::node::{self}; 5 | 6 | use std::collections::{HashMap}; 7 | use std::net::{SocketAddr}; 8 | 9 | struct ClientInfo { 10 | count: usize, 11 | } 12 | 13 | pub fn run(transport: Transport, addr: SocketAddr) { 14 | let (handler, listener) = node::split::<()>(); 15 | 16 | let mut clients: HashMap = HashMap::new(); 17 | 18 | match handler.network().listen(transport, addr) { 19 | Ok((_id, real_addr)) => println!("Server running at {} by {}", real_addr, transport), 20 | Err(_) => return println!("Can not listening at {} by {}", addr, transport), 21 | } 22 | 23 | listener.for_each(move |event| match event.network() { 24 | NetEvent::Connected(_, _) => (), // Only generated at connect() calls. 25 | NetEvent::Accepted(endpoint, _listener_id) => { 26 | // Only connection oriented protocols will generate this event 27 | clients.insert(endpoint, ClientInfo { count: 0 }); 28 | println!("Client ({}) connected (total clients: {})", endpoint.addr(), clients.len()); 29 | } 30 | NetEvent::Message(endpoint, input_data) => { 31 | let message: FromClientMessage = bincode::deserialize(&input_data).unwrap(); 32 | match message { 33 | FromClientMessage::Ping => { 34 | let message = match clients.get_mut(&endpoint) { 35 | Some(client) => { 36 | // For connection oriented protocols 37 | client.count += 1; 38 | println!("Ping from {}, {} times", endpoint.addr(), client.count); 39 | FromServerMessage::Pong(client.count) 40 | } 41 | None => { 42 | // For non-connection oriented protocols 43 | println!("Ping from {}", endpoint.addr()); 44 | FromServerMessage::UnknownPong 45 | } 46 | }; 47 | let output_data = bincode::serialize(&message).unwrap(); 48 | handler.network().send(endpoint, &output_data); 49 | } 50 | } 51 | } 52 | NetEvent::Disconnected(endpoint) => { 53 | // Only connection oriented protocols will generate this event 54 | clients.remove(&endpoint).unwrap(); 55 | println!( 56 | "Client ({}) disconnected (total clients: {})", 57 | endpoint.addr(), 58 | clients.len() 59 | ); 60 | } 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | # Based on crossterm repository 2 | name: message-io ci 3 | 4 | on: 5 | # Build master branch only 6 | push: 7 | branches: 8 | - master 9 | 10 | # Build pull requests targeting master branch only 11 | pull_request: 12 | branches: 13 | - master 14 | 15 | jobs: 16 | test: 17 | name: ${{ matrix.rust }} on ${{ matrix.os }}-${{matrix.platform}} 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | matrix: 21 | os: [ubuntu-latest, windows-2019, macOS-latest] 22 | rust: [stable, nightly] 23 | include: 24 | - rust: stable 25 | can-fail: false 26 | - rust: nightly 27 | can-fail: false 28 | steps: 29 | - name: Checkout Repository 30 | uses: actions/checkout@v1 31 | with: 32 | fetch-depth: 1 33 | - name: Install Rust 34 | uses: hecrj/setup-rust-action@master 35 | with: 36 | rust-version: ${{ matrix.rust }} 37 | components: rustfmt,clippy 38 | - name: Toolchain Information 39 | run: | 40 | rustc --version 41 | rustfmt --version 42 | rustup --version 43 | cargo --version 44 | - name: Check Formatting 45 | if: matrix.rust == 'nightly' 46 | run: cargo fmt --all -- --check 47 | continue-on-error: ${{ matrix.can-fail }} 48 | - name: Clippy 49 | run: cargo clippy -- -D clippy::all 50 | continue-on-error: ${{ matrix.can-fail }} 51 | - name: Check build 52 | run: cargo build 53 | continue-on-error: ${{ matrix.can-fail }} 54 | - name: Check test and examples 55 | run: cargo test -- --nocapture 56 | continue-on-error: ${{ matrix.can-fail }} 57 | - name: Check benchmarks (only compilation) 58 | run: cargo bench --no-run 59 | continue-on-error: ${{ matrix.can-fail }} 60 | - name: Test Packaging 61 | if: matrix.rust == 'stable' 62 | run: cargo package 63 | continue-on-error: ${{ matrix.can-fail }} 64 | test-cross: 65 | # websocket requires openssl that complicates cross compilation 66 | name: Build 67 | runs-on: ubuntu-latest 68 | strategy: 69 | matrix: 70 | target: 71 | - i686-unknown-linux-gnu 72 | steps: 73 | - uses: actions/checkout@v2 74 | - uses: actions-rs/toolchain@v1 75 | with: 76 | toolchain: stable 77 | target: ${{ matrix.target }} 78 | override: true 79 | - uses: actions-rs/cargo@v1 80 | with: 81 | use-cross: true 82 | command: build 83 | - uses: actions-rs/cargo@v1 84 | with: 85 | use-cross: true 86 | command: test 87 | - uses: actions-rs/cargo@v1 88 | with: 89 | use-cross: true 90 | command: bench 91 | args: --no-run 92 | -------------------------------------------------------------------------------- /examples/file-transfer/receiver.rs: -------------------------------------------------------------------------------- 1 | use super::common::{SenderMsg, ReceiverMsg}; 2 | 3 | use message_io::network::{NetEvent, Transport, Endpoint}; 4 | use message_io::node::{self}; 5 | 6 | use std::collections::{HashMap}; 7 | use std::fs::{File}; 8 | use std::io::{Write}; 9 | 10 | pub struct Transfer { 11 | file: File, 12 | name: String, 13 | current_size: usize, 14 | expected_size: usize, 15 | } 16 | 17 | pub fn run() { 18 | let (handler, listener) = node::split::<()>(); 19 | 20 | let listen_addr = "127.0.0.1:3005"; 21 | handler.network().listen(Transport::FramedTcp, listen_addr).unwrap(); 22 | println!("Receiver running by TCP at {}", listen_addr); 23 | 24 | let mut transfers: HashMap = HashMap::new(); 25 | 26 | listener.for_each(move |event| match event.network() { 27 | NetEvent::Connected(_, _) => unreachable!(), 28 | NetEvent::Accepted(_, _) => (), 29 | NetEvent::Message(endpoint, input_data) => { 30 | let message: SenderMsg = bincode::deserialize(&input_data).unwrap(); 31 | match message { 32 | SenderMsg::FileRequest(name, size) => { 33 | let able = match File::create(format!("{}.recv", name)) { 34 | Ok(file) => { 35 | println!("Accept file: '{}' with {} bytes", name, size); 36 | let transfer = 37 | Transfer { file, name, current_size: 0, expected_size: size }; 38 | transfers.insert(endpoint, transfer); 39 | true 40 | } 41 | Err(_) => { 42 | println!("Can not open the file to write"); 43 | false 44 | } 45 | }; 46 | 47 | let output_data = bincode::serialize(&ReceiverMsg::CanReceive(able)).unwrap(); 48 | handler.network().send(endpoint, &output_data); 49 | } 50 | SenderMsg::Chunk(data) => { 51 | let transfer = transfers.get_mut(&endpoint).unwrap(); 52 | transfer.file.write(&data).unwrap(); 53 | transfer.current_size += data.len(); 54 | 55 | let current = transfer.current_size as f32; 56 | let expected = transfer.expected_size as f32; 57 | let percentage = ((current / expected) * 100.0) as usize; 58 | print!("\rReceiving '{}': {}%", transfer.name, percentage); 59 | 60 | if transfer.expected_size == transfer.current_size { 61 | println!("\nFile '{}' received!", transfer.name); 62 | transfers.remove(&endpoint).unwrap(); 63 | } 64 | } 65 | } 66 | } 67 | NetEvent::Disconnected(endpoint) => { 68 | // Unexpected sender disconnection. Cleaning. 69 | if transfers.contains_key(&endpoint) { 70 | println!("\nUnexpected Sender disconnected"); 71 | transfers.remove(&endpoint); 72 | } 73 | } 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /examples/file-transfer/sender.rs: -------------------------------------------------------------------------------- 1 | use super::common::{SenderMsg, ReceiverMsg}; 2 | 3 | use message_io::network::{NetEvent, Transport}; 4 | use message_io::node::{self, NodeEvent}; 5 | 6 | use std::fs::{self, File}; 7 | use std::io::{Read}; 8 | use std::time::{Duration}; 9 | 10 | enum Signal { 11 | SendChunk, 12 | // Other signals here 13 | } 14 | 15 | const CHUNK_SIZE: usize = 65536; 16 | 17 | pub fn run(file_path: String) { 18 | let (handler, listener) = node::split(); 19 | 20 | let server_addr = "127.0.0.1:3005"; 21 | let (server_id, _) = handler.network().connect(Transport::FramedTcp, server_addr).unwrap(); 22 | 23 | let file_size = fs::metadata(&file_path).unwrap().len() as usize; 24 | let mut file = File::open(&file_path).unwrap(); 25 | let file_name: String = file_path.rsplit('/').into_iter().next().unwrap_or(&file_path).into(); 26 | 27 | let mut file_bytes_sent = 0; 28 | listener.for_each(move |event| match event { 29 | NodeEvent::Network(net_event) => match net_event { 30 | NetEvent::Connected(_, established) => { 31 | if established { 32 | println!("Sender connected by TCP at {}", server_addr); 33 | let request = SenderMsg::FileRequest(file_name.clone(), file_size); 34 | let output_data = bincode::serialize(&request).unwrap(); 35 | handler.network().send(server_id, &output_data); 36 | } 37 | else { 38 | println!("Can not connect to the receiver by TCP to {}", server_addr) 39 | } 40 | } 41 | NetEvent::Accepted(_, _) => unreachable!(), 42 | NetEvent::Message(_, input_data) => { 43 | let message: ReceiverMsg = bincode::deserialize(&input_data).unwrap(); 44 | match message { 45 | ReceiverMsg::CanReceive(can) => match can { 46 | true => handler.signals().send(Signal::SendChunk), // Start sending 47 | false => { 48 | handler.stop(); 49 | println!("The receiver can not receive the file :("); 50 | } 51 | }, 52 | } 53 | } 54 | NetEvent::Disconnected(_) => { 55 | handler.stop(); 56 | println!("\nReceiver disconnected"); 57 | } 58 | }, 59 | NodeEvent::Signal(signal) => match signal { 60 | Signal::SendChunk => { 61 | let mut data = [0; CHUNK_SIZE]; 62 | let bytes_read = file.read(&mut data).unwrap(); 63 | if bytes_read > 0 { 64 | let chunk = SenderMsg::Chunk(Vec::from(&data[0..bytes_read])); 65 | let output_data = bincode::serialize(&chunk).unwrap(); 66 | handler.network().send(server_id, &output_data); 67 | file_bytes_sent += bytes_read; 68 | 69 | let percentage = ((file_bytes_sent as f32 / file_size as f32) * 100.0) as usize; 70 | print!("\rSending '{}': {}%", file_name, percentage); 71 | 72 | handler.signals().send_with_timer(Signal::SendChunk, Duration::from_micros(10)); 73 | } 74 | else { 75 | println!("\nFile sent!"); 76 | handler.stop(); 77 | } 78 | } 79 | }, 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /src/network/loader.rs: -------------------------------------------------------------------------------- 1 | use crate::network::{TransportConnect, TransportListen}; 2 | 3 | use super::endpoint::{Endpoint}; 4 | use super::resource_id::{ResourceId}; 5 | use super::poll::{Poll, Readiness}; 6 | use super::remote_addr::{RemoteAddr}; 7 | use super::driver::{NetEvent, Driver, ActionController, EventProcessor}; 8 | use super::adapter::{Adapter, SendStatus}; 9 | 10 | use std::net::{SocketAddr}; 11 | use std::io::{self}; 12 | use std::panic::{UnwindSafe}; 13 | 14 | type Controller = Box; 15 | type Processor = Box; 16 | 17 | pub type ActionControllerList = Vec; 18 | pub type EventProcessorList = Vec; 19 | 20 | /// Used to configured the engine 21 | pub struct DriverLoader { 22 | poll: Poll, 23 | controllers: ActionControllerList, 24 | processors: EventProcessorList, 25 | } 26 | 27 | impl Default for DriverLoader { 28 | fn default() -> DriverLoader { 29 | Self { 30 | poll: Poll::default(), 31 | controllers: (0..ResourceId::MAX_ADAPTERS) 32 | .map(|_| Box::new(UnimplementedDriver) as Controller) 33 | .collect::>(), 34 | processors: (0..ResourceId::MAX_ADAPTERS) 35 | .map(|_| Box::new(UnimplementedDriver) as Processor) 36 | .collect(), 37 | } 38 | } 39 | } 40 | 41 | impl DriverLoader { 42 | /// Mount an adapter to create its driver associating it with an id. 43 | pub fn mount(&mut self, adapter_id: u8, adapter: impl Adapter + 'static) { 44 | let index = adapter_id as usize; 45 | 46 | let driver = Driver::new(adapter, adapter_id, &mut self.poll); 47 | 48 | self.controllers[index] = Box::new(driver.clone()) as Controller; 49 | self.processors[index] = Box::new(driver) as Processor; 50 | } 51 | 52 | /// Consume this instance to obtain the driver handles. 53 | pub fn take(self) -> (Poll, ActionControllerList, EventProcessorList) { 54 | (self.poll, self.controllers, self.processors) 55 | } 56 | } 57 | 58 | // The following unimplemented driver is used to fill 59 | // the invalid adapter id gaps in the controllers/processors lists. 60 | // It is faster and cleanest than to use an option that always must to be unwrapped. 61 | 62 | const UNIMPLEMENTED_DRIVER_ERR: &str = 63 | "The chosen adapter id doesn't reference an existing adapter"; 64 | 65 | struct UnimplementedDriver; 66 | impl ActionController for UnimplementedDriver { 67 | fn connect_with( 68 | &self, 69 | _: TransportConnect, 70 | _: RemoteAddr, 71 | ) -> io::Result<(Endpoint, SocketAddr)> { 72 | panic!("{}", UNIMPLEMENTED_DRIVER_ERR); 73 | } 74 | 75 | fn listen_with( 76 | &self, 77 | _: TransportListen, 78 | _: SocketAddr, 79 | ) -> io::Result<(ResourceId, SocketAddr)> { 80 | panic!("{}", UNIMPLEMENTED_DRIVER_ERR); 81 | } 82 | 83 | fn send(&self, _: Endpoint, _: &[u8]) -> SendStatus { 84 | panic!("{}", UNIMPLEMENTED_DRIVER_ERR); 85 | } 86 | 87 | fn remove(&self, _: ResourceId) -> bool { 88 | panic!("{}", UNIMPLEMENTED_DRIVER_ERR); 89 | } 90 | 91 | fn is_ready(&self, _: ResourceId) -> Option { 92 | panic!("{}", UNIMPLEMENTED_DRIVER_ERR); 93 | } 94 | } 95 | 96 | impl EventProcessor for UnimplementedDriver { 97 | fn process(&self, _: ResourceId, _: Readiness, _: &mut dyn FnMut(NetEvent<'_>)) { 98 | panic!("{}", UNIMPLEMENTED_DRIVER_ERR); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/util/thread.rs: -------------------------------------------------------------------------------- 1 | use std::thread::{self, JoinHandle}; 2 | 3 | /// A comprensive error message to notify that the error shown is from other thread. 4 | pub const OTHER_THREAD_ERR: &str = "Avoid this 'panicked_at' error. \ 5 | This error is shown because other thread has panicked \ 6 | You can safety skip this error."; 7 | 8 | /// Thread similar to the std, but with a name that can be nested. 9 | pub struct NamespacedThread { 10 | namespace: String, 11 | join_handle: Option>, 12 | } 13 | 14 | impl NamespacedThread { 15 | /// Similar to [`thread::spawn()`] but with a name. 16 | pub fn spawn(name: &str, f: F) -> Self 17 | where 18 | F: FnOnce() -> T, 19 | F: Send + 'static, 20 | { 21 | let namespace = format!("{}/{}", thread::current().name().unwrap_or(""), name); 22 | Self { 23 | namespace: namespace.clone(), 24 | join_handle: Some( 25 | thread::Builder::new() 26 | .name(namespace.clone()) 27 | .spawn(move || { 28 | log::trace!("Thread [{}] spawned", namespace); 29 | f() 30 | }) 31 | .unwrap(), 32 | ), 33 | } 34 | } 35 | 36 | /// Wait the thread to finish. 37 | pub fn join(&mut self) -> T { 38 | log::trace!("Join thread [{}] ...", self.namespace); 39 | let content = self.join_handle.take().unwrap().join().expect(OTHER_THREAD_ERR); 40 | log::trace!("Joined thread [{}]", self.namespace); 41 | content 42 | } 43 | 44 | /// Wait the thread to finish. 45 | /// Returns the inner `T` value if never was joined, `None` otherwise 46 | pub fn try_join(&mut self) -> Option { 47 | if self.join_handle.is_some() { 48 | return Some(self.join()); 49 | } 50 | None 51 | } 52 | } 53 | 54 | impl Drop for NamespacedThread { 55 | fn drop(&mut self) { 56 | self.try_join(); 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::*; 63 | use std::time::{Duration}; 64 | use std::sync::atomic::{AtomicBool, Ordering}; 65 | use std::sync::{Arc}; 66 | 67 | #[test] 68 | fn basic_usage() { 69 | let called = Arc::new(AtomicBool::new(false)); 70 | let mut thread = { 71 | let called = called.clone(); 72 | NamespacedThread::spawn("test", move || { 73 | std::thread::sleep(Duration::from_millis(500)); 74 | called.store(true, Ordering::Relaxed); 75 | }) 76 | }; 77 | 78 | std::thread::sleep(Duration::from_millis(250)); 79 | assert!(!called.load(Ordering::Relaxed)); 80 | std::thread::sleep(Duration::from_millis(500)); 81 | assert!(called.load(Ordering::Relaxed)); 82 | thread.join(); 83 | } 84 | 85 | #[test] 86 | fn join_result() { 87 | let called = Arc::new(AtomicBool::new(false)); 88 | let mut thread = { 89 | let called = called.clone(); 90 | NamespacedThread::spawn("test", move || { 91 | std::thread::sleep(Duration::from_millis(500)); 92 | called.store(true, Ordering::Relaxed); 93 | "result" 94 | }) 95 | }; 96 | assert_eq!("result", thread.join()); 97 | assert!(called.load(Ordering::Relaxed)); 98 | } 99 | 100 | #[test] 101 | fn drop_implies_join() { 102 | let called = Arc::new(AtomicBool::new(false)); 103 | let thread = { 104 | let called = called.clone(); 105 | NamespacedThread::spawn("test", move || { 106 | std::thread::sleep(Duration::from_millis(500)); 107 | called.store(true, Ordering::Relaxed); 108 | }) 109 | }; 110 | drop(thread); 111 | assert!(called.load(Ordering::Relaxed)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/network/endpoint.rs: -------------------------------------------------------------------------------- 1 | use super::resource_id::{ResourceId}; 2 | 3 | use std::net::{SocketAddr}; 4 | 5 | /// Information to identify the remote endpoint. 6 | /// The endpoint is used mainly as a connection identified. 7 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 8 | pub struct Endpoint { 9 | resource_id: ResourceId, 10 | addr: SocketAddr, 11 | } 12 | 13 | impl Endpoint { 14 | /// Creates a new Endpoint to use in non connection oriented protocols. 15 | /// 16 | /// For non connection-oriented protocols, as *UDP*, the endpoint can be created manually 17 | /// from a **listener resource** to send messages to different address without 18 | /// creating a connection. 19 | /// 20 | /// For connection oriented protocol, creating manually an endpoint is not allowed. 21 | /// 22 | /// # Example 23 | /// ```rust 24 | /// use message_io::node::{self, NodeEvent}; 25 | /// use message_io::network::{Transport, Endpoint, NetEvent}; 26 | /// 27 | /// let (handler, listener) = node::split::<()>(); 28 | /// handler.signals().send_with_timer((), std::time::Duration::from_secs(1)); //timeout 29 | /// 30 | /// let listen_addr = "127.0.0.1:0"; 31 | /// let (receiver_id_1, addr_1) = handler.network().listen(Transport::Udp, listen_addr).unwrap(); 32 | /// let (receiver_id_2, addr_2) = handler.network().listen(Transport::Udp, listen_addr).unwrap(); 33 | /// let (sender_id, _) = handler.network().listen(Transport::Udp, listen_addr).unwrap(); 34 | /// 35 | /// //addr_1 and addr_2 contain the addresses with the listening ports. 36 | /// handler.network().send(Endpoint::from_listener(sender_id, addr_1), &[23]); 37 | /// handler.network().send(Endpoint::from_listener(sender_id, addr_2), &[42]); 38 | /// 39 | /// let (mut msg_1, mut msg_2) = (0, 0); 40 | /// listener.for_each(|event| match event { 41 | /// NodeEvent::Signal(_) => handler.stop(), 42 | /// NodeEvent::Network(net_event) => match net_event { 43 | /// NetEvent::Message(endpoint, message) => match endpoint.resource_id() { 44 | /// id if id == receiver_id_1 => msg_1 = message[0], 45 | /// id if id == receiver_id_2 => msg_2 = message[0], 46 | /// _ => unreachable!(), 47 | /// } 48 | /// _ => unreachable!(), 49 | /// } 50 | /// }); 51 | /// 52 | /// assert_eq!((msg_1, msg_2), (23, 42)); 53 | /// ``` 54 | pub fn from_listener(id: ResourceId, addr: SocketAddr) -> Self { 55 | // Only local resources allowed 56 | assert_eq!(id.resource_type(), super::resource_id::ResourceType::Local); 57 | 58 | // Only non connection-oriented transport protocols allowed 59 | assert!(!super::transport::Transport::from(id.adapter_id()).is_connection_oriented()); 60 | 61 | Endpoint::new(id, addr) 62 | } 63 | 64 | pub(crate) fn new(resource_id: ResourceId, addr: SocketAddr) -> Self { 65 | Self { resource_id, addr } 66 | } 67 | 68 | /// Returns the inner network resource id used by this endpoint. 69 | /// It is not necessary to be unique for each endpoint if some of them shared the resource 70 | /// (an example of this is the different endpoints generated by when you listen by udp). 71 | pub fn resource_id(&self) -> ResourceId { 72 | self.resource_id 73 | } 74 | 75 | /// Returns the peer address of the endpoint. 76 | pub fn addr(&self) -> SocketAddr { 77 | self.addr 78 | } 79 | } 80 | 81 | impl std::fmt::Display for Endpoint { 82 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 83 | write!(f, "{} {}", self.resource_id, self.addr) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | use crate::network::resource_id::{ResourceType, ResourceIdGenerator}; 91 | use crate::network::transport::{Transport}; 92 | 93 | #[test] 94 | fn from_local_non_connection_oriented() { 95 | let addr = "0.0.0.0:0".parse().unwrap(); 96 | let generator = ResourceIdGenerator::new(Transport::Udp.id(), ResourceType::Local); 97 | Endpoint::from_listener(generator.generate(), addr); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /examples/distributed/discovery_server.rs: -------------------------------------------------------------------------------- 1 | use super::common::{Message}; 2 | 3 | use message_io::network::{NetEvent, Transport, Endpoint}; 4 | use message_io::node::{self, NodeHandler, NodeListener}; 5 | 6 | use std::net::{SocketAddr}; 7 | use std::collections::{HashMap}; 8 | use std::io::{self}; 9 | 10 | struct ParticipantInfo { 11 | addr: SocketAddr, 12 | endpoint: Endpoint, 13 | } 14 | 15 | pub struct DiscoveryServer { 16 | handler: NodeHandler<()>, 17 | node_listener: Option>, 18 | participants: HashMap, 19 | } 20 | 21 | impl DiscoveryServer { 22 | pub fn new() -> io::Result { 23 | let (handler, node_listener) = node::split::<()>(); 24 | 25 | let listen_addr = "127.0.0.1:5000"; 26 | handler.network().listen(Transport::FramedTcp, listen_addr)?; 27 | 28 | println!("Discovery server running at {}", listen_addr); 29 | 30 | Ok(DiscoveryServer { 31 | handler, 32 | node_listener: Some(node_listener), 33 | participants: HashMap::new(), 34 | }) 35 | } 36 | 37 | pub fn run(mut self) { 38 | let node_listener = self.node_listener.take().unwrap(); 39 | node_listener.for_each(move |event| match event.network() { 40 | NetEvent::Connected(_, _) => unreachable!(), // There is no connect() calls. 41 | NetEvent::Accepted(_, _) => (), // All endpoint accepted 42 | NetEvent::Message(endpoint, input_data) => { 43 | let message: Message = bincode::deserialize(&input_data).unwrap(); 44 | match message { 45 | Message::RegisterParticipant(name, addr) => { 46 | self.register(&name, addr, endpoint); 47 | } 48 | Message::UnregisterParticipant(name) => { 49 | self.unregister(&name); 50 | } 51 | _ => unreachable!(), 52 | } 53 | } 54 | NetEvent::Disconnected(endpoint) => { 55 | // Participant disconnection without explict unregistration. 56 | // We must remove from the registry too. 57 | let participant = 58 | self.participants.iter().find(|(_, info)| info.endpoint == endpoint); 59 | 60 | if let Some(participant) = participant { 61 | let name = participant.0.to_string(); 62 | self.unregister(&name); 63 | } 64 | } 65 | }); 66 | } 67 | 68 | fn register(&mut self, name: &str, addr: SocketAddr, endpoint: Endpoint) { 69 | if !self.participants.contains_key(name) { 70 | // Update the new participant with the whole participants information 71 | let list = 72 | self.participants.iter().map(|(name, info)| (name.clone(), info.addr)).collect(); 73 | 74 | let message = Message::ParticipantList(list); 75 | let output_data = bincode::serialize(&message).unwrap(); 76 | self.handler.network().send(endpoint, &output_data); 77 | 78 | // Notify other participants about this new participant 79 | let message = Message::ParticipantNotificationAdded(name.to_string(), addr); 80 | let output_data = bincode::serialize(&message).unwrap(); 81 | for participant in &mut self.participants { 82 | self.handler.network().send(participant.1.endpoint, &output_data); 83 | } 84 | 85 | // Register participant 86 | self.participants.insert(name.to_string(), ParticipantInfo { addr, endpoint }); 87 | println!("Added participant '{}' with ip {}", name, addr); 88 | } 89 | else { 90 | println!( 91 | "Participant with name '{}' already exists, please registry with another name", 92 | name 93 | ); 94 | } 95 | } 96 | 97 | fn unregister(&mut self, name: &str) { 98 | if let Some(info) = self.participants.remove(name) { 99 | // Notify other participants about this removed participant 100 | let message = Message::ParticipantNotificationRemoved(name.to_string()); 101 | let output_data = bincode::serialize(&message).unwrap(); 102 | for participant in &mut self.participants { 103 | self.handler.network().send(participant.1.endpoint, &output_data); 104 | } 105 | println!("Removed participant '{}' with ip {}", name, info.addr); 106 | } 107 | else { 108 | println!("Can not unregister an non-existent participant with name '{}'", name); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /docs/performance_benchmarks.md: -------------------------------------------------------------------------------- 1 | # Performance 2 | 3 | `message-io` is focused on getting the best performance, 4 | adding the minimal possible overhead over raw OS socket. 5 | As for performance features, it offers: 6 | - Zero-copy read/write messages: Messages sent and received pass their data reference (`&[u8]`) 7 | through the library, from the user-space to OS socket and vice-versa without copies. 8 | This means that the overhead the library adds is independent of the size of the message, 9 | because the library only copies the pointer to the data, no matter the longer this data is. 10 | - Full-duplex: From two different threads, you can simultaneously write and read over the same socket. 11 | - Multiwriter: Over the same [node](basic_concepts.md), you can write simultaneously from any number of different sockets without blocking for each other. 12 | - One internal thread with non-blocking sockets: 13 | Creating new connections and listeners do not add new threads. 14 | All sockets are managed from one thread, saving resources at scaling. 15 | 16 | ## Benchmarks 17 | 18 | The benchmark compares two dimensions: 19 | - *Vertical*: among different transports. How they are compared among them. 20 | - *Horizontal*: between a *native usage* and its usage by `message-io`. 21 | A *native usage* is represented here as the **most basic usage** of a transport in a blocking way 22 | between two sockets, with zero overhead. 23 | When checking the results, take into account that `message-io` manages for you a pool of sockets that behaves in a non-blocking way waking from a OS poll. 24 | It adds some delay to handle these things. 25 | However, in most of the network applications, you will need to build some kind of socket management on top of the *native usage* by yourself. So, in practice, not using `message-io` would also imply an overhead by implementing a management layer. 26 | 27 | *Note that the network benchmark results can vary slightly among different runs 28 | since it depends considerably on the OS load at the moment of execution.* 29 | 30 | ### Throughput benchmarks 31 | The throughput is the amount of data you can transfer by a second, measured in bytes per second. 32 | It starts when the sender sends the first byte and ends when the receiver receives the last byte. 33 | 34 |

35 | 36 |

37 | 38 | The following results are measured for the transmision of 1GB of data by localhost: 39 | 40 | | Transport | native | message-io | efficiency | 41 | |:----------:|:--------:|:----------:|:----------:| 42 | | UDP | 7.1 GB/s | 5.9 GB/s | ~83% | 43 | | TCP | 6.4 GB/s | 5.2 GB/s | ~81% | 44 | | Framed TCP | 5.5 GB/s | 5.0 GB/s | ~91% | 45 | | Web Socket | 590 MB/s | 560 MB/s | ~95% | 46 | 47 | Take into account that the throughput is measured by localhost. 48 | In localhost the computer makes few more than a data copy, 49 | so the measurement is not hidden by network latency. 50 | This means that the time difference between the *native usage* and `message-io` 51 | measures exactly the overhead that `message-io` adds over that *native usage*. 52 | In this prejudicial conditions for `message-io`, it maintains an efficiency of `80%`-`90%` which in practice (in a real network environment with minimal latency), is higher. 53 | 54 | As is mentioned above, note that the *basic usage* is a raw and basic usage of the sockets. 55 | Any real application will add some kind of management over them, adding also an overhead. 56 | 57 | If you want to test it yourself, see the [throughput](../examples/throughput) example. 58 | 59 | ### Latency benchmarks 60 | The latency can be measured as the time a byte takes to be trasferred. 61 | It starts when the sender sends a byte and ends when the receiver receives that byte. 62 | 63 |

64 | 65 |

66 | 67 | The following results are measured by transferring 1-byte by localhost: 68 | 69 | | Transport | native | message-io | overhead | 70 | |:----------:|:------:|:----------:|:----------:| 71 | | UDP | 1.2 us | 2.1 us | + ~0.9 us | 72 | | TCP | 2.6 us | 3.5 us | + ~0.9 us | 73 | | Framed TCP | 5.2 us | 6.6 us | + ~1.4 us | 74 | | Web Socket | 9.1 us | 10.1 us | + ~1.0 us | 75 | 76 | Depending on the transport used, `message-io` adds around `1us` of overhead per chunk of data transsmision. 77 | Because it is zero-copy at reading/writing messages, 78 | this overhead is constant and independently of the size of that chunk of data. 79 | The library only copies the pointer to the data. 80 | It means, for example, that sending and receiving a message of 1 Byte or 64KB by *UDP* (if the MTU supports that size), only adds around `0.9us` of overhead to the entire data transsmision. 81 | 82 | *Note that *TCP* based protocols could split internally your message into several chunks of data.* 83 | 84 | If you want to test it yourself, execute `cargo bench`. 85 | -------------------------------------------------------------------------------- /examples/distributed/participant.rs: -------------------------------------------------------------------------------- 1 | use super::common::{Message}; 2 | 3 | use message_io::network::{NetEvent, Transport, Endpoint}; 4 | use message_io::node::{self, NodeHandler, NodeListener}; 5 | 6 | use std::net::{SocketAddr}; 7 | use std::collections::{HashMap}; 8 | use std::io::{self}; 9 | 10 | pub struct Participant { 11 | handler: NodeHandler<()>, 12 | node_listener: Option>, 13 | name: String, 14 | discovery_endpoint: Endpoint, 15 | public_addr: SocketAddr, 16 | known_participants: HashMap, // Used only for free resources later 17 | greetings: HashMap, 18 | } 19 | 20 | impl Participant { 21 | pub fn new(name: &str) -> io::Result { 22 | let (handler, node_listener) = node::split(); 23 | 24 | // A node_listener for any other participant that want to establish connection. 25 | // Returned 'listen_addr' contains the port that the OS gives for us when we put a 0. 26 | let listen_addr = "127.0.0.1:0"; 27 | let (_, listen_addr) = handler.network().listen(Transport::FramedTcp, listen_addr)?; 28 | 29 | let discovery_addr = "127.0.0.1:5000"; // Connection to the discovery server. 30 | let (endpoint, _) = handler.network().connect(Transport::FramedTcp, discovery_addr)?; 31 | 32 | Ok(Participant { 33 | handler, 34 | node_listener: Some(node_listener), 35 | name: name.to_string(), 36 | discovery_endpoint: endpoint, 37 | public_addr: listen_addr, 38 | known_participants: HashMap::new(), 39 | greetings: HashMap::new(), 40 | }) 41 | } 42 | 43 | pub fn run(mut self) { 44 | // Register this participant into the discovery server 45 | let node_listener = self.node_listener.take().unwrap(); 46 | node_listener.for_each(move |event| match event.network() { 47 | NetEvent::Connected(endpoint, established) => { 48 | if endpoint == self.discovery_endpoint { 49 | if established { 50 | let message = 51 | Message::RegisterParticipant(self.name.clone(), self.public_addr); 52 | let output_data = bincode::serialize(&message).unwrap(); 53 | self.handler.network().send(self.discovery_endpoint, &output_data); 54 | } 55 | else { 56 | println!("Can not connect to the discovery server"); 57 | } 58 | } 59 | else { 60 | // Participant endpoint 61 | let (name, message) = self.greetings.remove(&endpoint).unwrap(); 62 | if established { 63 | let greetings = format!("Hi '{}', {}", name, message); 64 | let message = Message::Greetings(self.name.clone(), greetings); 65 | let output_data = bincode::serialize(&message).unwrap(); 66 | self.handler.network().send(endpoint, &output_data); 67 | self.known_participants.insert(name.clone(), endpoint); 68 | } 69 | } 70 | } 71 | NetEvent::Accepted(_, _) => (), 72 | NetEvent::Message(_, input_data) => { 73 | let message: Message = bincode::deserialize(&input_data).unwrap(); 74 | match message { 75 | Message::ParticipantList(participants) => { 76 | println!("Participant list received ({} participants)", participants.len()); 77 | for (name, addr) in participants { 78 | let text = "I see you in the participant list"; 79 | self.discovered_participant(&name, addr, text); 80 | } 81 | } 82 | Message::ParticipantNotificationAdded(name, addr) => { 83 | println!("New participant '{}' in the network", name); 84 | self.discovered_participant(&name, addr, "welcome to the network!"); 85 | } 86 | Message::ParticipantNotificationRemoved(name) => { 87 | println!("Removed participant '{}' from the network", name); 88 | if let Some(endpoint) = self.known_participants.remove(&name) { 89 | self.handler.network().remove(endpoint.resource_id()); 90 | } 91 | } 92 | Message::Greetings(name, greetings) => { 93 | println!("'{}' says: {}", name, greetings); 94 | } 95 | _ => unreachable!(), 96 | } 97 | } 98 | NetEvent::Disconnected(endpoint) => { 99 | if endpoint == self.discovery_endpoint { 100 | println!("Discovery server disconnected, closing"); 101 | self.handler.stop(); 102 | } 103 | } 104 | }); 105 | } 106 | 107 | fn discovered_participant(&mut self, name: &str, addr: SocketAddr, text: &str) { 108 | let (endpoint, _) = self.handler.network().connect(Transport::FramedTcp, addr).unwrap(); 109 | // Save the necessary info to send the message when the connection is established. 110 | self.greetings.insert(endpoint, (name.into(), text.into())); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/network/poll.rs: -------------------------------------------------------------------------------- 1 | use super::resource_id::{ResourceId, ResourceType, ResourceIdGenerator}; 2 | 3 | use mio::{Poll as MioPoll, Interest, Token, Events, Registry, Waker}; 4 | use mio::event::{Source}; 5 | 6 | use std::time::{Duration}; 7 | use std::sync::{Arc}; 8 | use std::io::{ErrorKind}; 9 | 10 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 11 | /// Used for the adapter implementation. 12 | /// Specify the kind of event that is available for a resource. 13 | pub enum Readiness { 14 | /// The resource is available to write 15 | Write, 16 | 17 | /// The resource is available to read (has any content to read). 18 | Read, 19 | } 20 | 21 | pub enum PollEvent { 22 | Network(ResourceId, Readiness), 23 | Waker, 24 | } 25 | 26 | impl From for ResourceId { 27 | fn from(token: Token) -> Self { 28 | (token.0 >> Poll::RESERVED_BITS).into() 29 | } 30 | } 31 | 32 | impl From for Token { 33 | fn from(id: ResourceId) -> Self { 34 | Token((id.raw() << Poll::RESERVED_BITS) | 1) 35 | } 36 | } 37 | 38 | pub struct Poll { 39 | mio_poll: MioPoll, 40 | events: Events, 41 | #[allow(dead_code)] //TODO: remove it with poll native event support 42 | waker: Arc, 43 | } 44 | 45 | impl Default for Poll { 46 | fn default() -> Self { 47 | let mio_poll = MioPoll::new().unwrap(); 48 | Self { 49 | waker: Arc::new(Waker::new(mio_poll.registry(), Self::WAKER_TOKEN).unwrap()), 50 | mio_poll, 51 | events: Events::with_capacity(Self::EVENTS_SIZE), 52 | } 53 | } 54 | } 55 | 56 | impl Poll { 57 | const EVENTS_SIZE: usize = 1024; 58 | const RESERVED_BITS: usize = 1; 59 | const WAKER_TOKEN: Token = Token(0); 60 | 61 | pub fn process_event(&mut self, timeout: Option, mut event_callback: C) 62 | where C: FnMut(PollEvent) { 63 | loop { 64 | match self.mio_poll.poll(&mut self.events, timeout) { 65 | Ok(()) => { 66 | for mio_event in &self.events { 67 | if Self::WAKER_TOKEN == mio_event.token() { 68 | log::trace!("POLL WAKER EVENT"); 69 | event_callback(PollEvent::Waker); 70 | } 71 | else { 72 | let id = ResourceId::from(mio_event.token()); 73 | if mio_event.is_readable() { 74 | log::trace!("POLL EVENT (R): {}", id); 75 | event_callback(PollEvent::Network(id, Readiness::Read)); 76 | } 77 | if mio_event.is_writable() { 78 | log::trace!("POLL EVENT (W): {}", id); 79 | event_callback(PollEvent::Network(id, Readiness::Write)); 80 | } 81 | } 82 | } 83 | break; 84 | } 85 | Err(ref err) if err.kind() == ErrorKind::Interrupted => continue, 86 | Err(ref err) => panic!("{}: No error here", err), 87 | } 88 | } 89 | } 90 | 91 | pub fn create_registry(&mut self, adapter_id: u8, resource_type: ResourceType) -> PollRegistry { 92 | PollRegistry::new(adapter_id, resource_type, self.mio_poll.registry().try_clone().unwrap()) 93 | } 94 | 95 | #[allow(dead_code)] //TODO: remove it with poll native event support 96 | pub fn create_waker(&mut self) -> PollWaker { 97 | PollWaker::new(self.waker.clone()) 98 | } 99 | } 100 | 101 | pub struct PollRegistry { 102 | id_generator: Arc, 103 | registry: Registry, 104 | } 105 | 106 | impl PollRegistry { 107 | fn new(adapter_id: u8, resource_type: ResourceType, registry: Registry) -> Self { 108 | Self { 109 | id_generator: Arc::new(ResourceIdGenerator::new(adapter_id, resource_type)), 110 | registry, 111 | } 112 | } 113 | 114 | pub fn add(&self, source: &mut dyn Source, write_readiness: bool) -> ResourceId { 115 | let id = self.id_generator.generate(); 116 | let interest = match write_readiness { 117 | true => Interest::READABLE | Interest::WRITABLE, 118 | false => Interest::READABLE, 119 | }; 120 | self.registry.register(source, id.into(), interest).unwrap(); 121 | id 122 | } 123 | 124 | pub fn remove(&self, source: &mut dyn Source) { 125 | self.registry.deregister(source).unwrap() 126 | } 127 | } 128 | 129 | impl Clone for PollRegistry { 130 | fn clone(&self) -> Self { 131 | Self { 132 | id_generator: self.id_generator.clone(), 133 | registry: self.registry.try_clone().unwrap(), 134 | } 135 | } 136 | } 137 | 138 | #[allow(dead_code)] //TODO: remove it with poll native event support 139 | pub struct PollWaker { 140 | waker: Arc, 141 | } 142 | 143 | impl PollWaker { 144 | #[allow(dead_code)] //TODO: remove it with poll native event support 145 | fn new(waker: Arc) -> Self { 146 | Self { waker } 147 | } 148 | 149 | #[allow(dead_code)] //TODO: remove it with poll native event support 150 | pub fn wake(&self) { 151 | self.waker.wake().unwrap(); 152 | log::trace!("Wake poll..."); 153 | } 154 | } 155 | 156 | impl Clone for PollWaker { 157 | fn clone(&self) -> Self { 158 | Self { waker: self.waker.clone() } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /benches/latency.rs: -------------------------------------------------------------------------------- 1 | use message_io::network::{self, Transport, NetworkController, NetworkProcessor, Endpoint}; 2 | use message_io::util::thread::{NamespacedThread}; 3 | use message_io::util::encoding::{self, Decoder, MAX_ENCODED_SIZE}; 4 | 5 | use criterion::{criterion_group, criterion_main, Criterion}; 6 | 7 | #[cfg(feature = "websocket")] 8 | use tungstenite::{Message, connect as ws_connect, accept as ws_accept}; 9 | #[cfg(feature = "websocket")] 10 | use url::{Url}; 11 | 12 | use std::net::{TcpListener, TcpStream, UdpSocket}; 13 | use std::time::{Duration}; 14 | use std::sync::{ 15 | Arc, 16 | atomic::{AtomicBool, Ordering}, 17 | }; 18 | use std::io::{Write, Read}; 19 | 20 | lazy_static::lazy_static! { 21 | static ref TIMEOUT: Duration = Duration::from_millis(1); 22 | } 23 | 24 | fn init_connection(transport: Transport) -> (NetworkController, NetworkProcessor, Endpoint) { 25 | let (controller, mut processor) = network::split(); 26 | 27 | let running = Arc::new(AtomicBool::new(true)); 28 | let mut thread = { 29 | let running = running.clone(); 30 | NamespacedThread::spawn("perf-listening", move || { 31 | while running.load(Ordering::Relaxed) { 32 | processor.process_poll_event(Some(*TIMEOUT), |_| ()); 33 | } 34 | processor 35 | }) 36 | }; 37 | 38 | let receiver_addr = controller.listen(transport, "127.0.0.1:0").unwrap().1; 39 | let receiver = controller.connect_sync(transport, receiver_addr).unwrap().0; 40 | 41 | running.store(false, Ordering::Relaxed); 42 | let processor = thread.join(); 43 | // From here, the connection is performed independently of the transport used 44 | 45 | (controller, processor, receiver) 46 | } 47 | 48 | fn latency_by(c: &mut Criterion, transport: Transport) { 49 | let msg = format!("latency by {}", transport); 50 | c.bench_function(&msg, |b| { 51 | let (controller, mut processor, endpoint) = init_connection(transport); 52 | 53 | b.iter(|| { 54 | controller.send(endpoint, &[0xFF]); 55 | processor.process_poll_event(Some(*TIMEOUT), |_| ()); 56 | }); 57 | }); 58 | } 59 | 60 | fn latency_by_native_udp(c: &mut Criterion) { 61 | let msg = format!("latency by native Udp"); 62 | c.bench_function(&msg, |b| { 63 | let receiver = UdpSocket::bind("127.0.0.1:0").unwrap(); 64 | let addr = receiver.local_addr().unwrap(); 65 | 66 | let sender = UdpSocket::bind("127.0.0.1:0").unwrap(); 67 | sender.connect(addr).unwrap(); 68 | 69 | let mut buffer: [u8; 1] = [0; 1]; 70 | 71 | b.iter(|| { 72 | sender.send(&[0xFF]).unwrap(); 73 | receiver.recv(&mut buffer).unwrap(); 74 | }); 75 | }); 76 | } 77 | 78 | fn latency_by_native_tcp(c: &mut Criterion) { 79 | let msg = format!("latency by native Tcp"); 80 | c.bench_function(&msg, |b| { 81 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 82 | let addr = listener.local_addr().unwrap(); 83 | 84 | let mut sender = TcpStream::connect(addr).unwrap(); 85 | let (mut receiver, _) = listener.accept().unwrap(); 86 | 87 | let mut buffer: [u8; 1] = [0; 1]; 88 | 89 | b.iter(|| { 90 | sender.write(&[0xFF]).unwrap(); 91 | receiver.read(&mut buffer).unwrap(); 92 | }); 93 | }); 94 | } 95 | 96 | fn latency_by_native_framed_tcp(c: &mut Criterion) { 97 | let msg = format!("latency by native FramedTcp"); 98 | c.bench_function(&msg, |b| { 99 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 100 | let addr = listener.local_addr().unwrap(); 101 | 102 | let mut sender = TcpStream::connect(addr).unwrap(); 103 | let (mut receiver, _) = listener.accept().unwrap(); 104 | 105 | let mut buffer: [u8; 1] = [0; 1]; 106 | let mut framming = [0; MAX_ENCODED_SIZE]; // used to avoid a heap allocation 107 | let mut decoder = Decoder::default(); 108 | 109 | b.iter(|| { 110 | let encoded_size = encoding::encode_size(&[0xFF], &mut framming); 111 | sender.write(&encoded_size).unwrap(); 112 | sender.write(&[0xFF]).unwrap(); 113 | 114 | let mut message_received = false; 115 | while !message_received { 116 | let bytes = receiver.read(&mut buffer).unwrap(); 117 | decoder.decode(&buffer[0..bytes], |_decoded_data| message_received = true); 118 | } 119 | }); 120 | }); 121 | } 122 | 123 | #[cfg(feature = "websocket")] 124 | fn latency_by_native_web_socket(c: &mut Criterion) { 125 | let msg = format!("latency by native Ws"); 126 | c.bench_function(&msg, |b| { 127 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 128 | let addr = listener.local_addr().unwrap(); 129 | 130 | let mut listen_thread = NamespacedThread::spawn("perf-listening", move || { 131 | ws_accept(listener.accept().unwrap().0).unwrap() 132 | }); 133 | 134 | let url_addr = format!("ws://{}/socket", addr); 135 | let (mut sender, _) = ws_connect(Url::parse(&url_addr).unwrap()).unwrap(); 136 | 137 | let mut receiver = listen_thread.join(); 138 | 139 | let message = vec![0xFF]; 140 | 141 | b.iter(|| { 142 | sender.send(Message::Binary(message.clone().into())).unwrap(); 143 | receiver.read().unwrap(); 144 | }); 145 | }); 146 | } 147 | 148 | fn latency(c: &mut Criterion) { 149 | #[cfg(feature = "udp")] 150 | latency_by(c, Transport::Udp); 151 | #[cfg(feature = "tcp")] 152 | latency_by(c, Transport::Tcp); 153 | #[cfg(feature = "tcp")] 154 | latency_by(c, Transport::FramedTcp); 155 | #[cfg(feature = "websocket")] 156 | latency_by(c, Transport::Ws); 157 | 158 | #[cfg(feature = "udp")] 159 | latency_by_native_udp(c); 160 | #[cfg(feature = "tcp")] 161 | latency_by_native_tcp(c); 162 | #[cfg(feature = "tcp")] 163 | latency_by_native_framed_tcp(c); 164 | #[cfg(feature = "websocket")] 165 | latency_by_native_web_socket(c); 166 | } 167 | 168 | criterion_group!(benches, latency); 169 | criterion_main!(benches); 170 | -------------------------------------------------------------------------------- /src/network/remote_addr.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize}; 2 | 3 | use std::net::{SocketAddr, ToSocketAddrs, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; 4 | use std::io::{self}; 5 | 6 | /// An struct that contains a remote address. 7 | /// It can be Either, a [`SocketAddr`] as usual or a `String` used for protocols 8 | /// that needs more than a `SocketAddr` to get connected (e.g. WebSocket) 9 | /// It is usually used in 10 | /// [`NetworkController::connect()`](crate::network::NetworkController::connect()) 11 | /// to specify the remote address. 12 | #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Debug)] 13 | pub enum RemoteAddr { 14 | Socket(SocketAddr), 15 | Str(String), 16 | } 17 | 18 | impl RemoteAddr { 19 | /// Check if the `RemoteAddr` is a [`SocketAddr`]. 20 | pub fn is_socket_addr(&self) -> bool { 21 | matches!(self, RemoteAddr::Socket(_)) 22 | } 23 | 24 | /// Check if the `RemoteAddr` is a string. 25 | pub fn is_string(&self) -> bool { 26 | matches!(self, RemoteAddr::Socket(_)) 27 | } 28 | 29 | /// Extract the [`SocketAddr`]. 30 | /// This function panics if the `RemoteAddr` do not represent a `SocketAddr`. 31 | pub fn socket_addr(&self) -> &SocketAddr { 32 | match self { 33 | RemoteAddr::Socket(addr) => addr, 34 | _ => panic!("The RemoteAddr must be a SocketAddr"), 35 | } 36 | } 37 | 38 | /// Extract the string. 39 | /// This function panics if the `RemoteAddr` is not a `Str` variant. 40 | pub fn string(&self) -> &str { 41 | match self { 42 | RemoteAddr::Str(addr) => addr, 43 | _ => panic!("The RemoteAddr must be a String"), 44 | } 45 | } 46 | } 47 | 48 | impl ToSocketAddrs for RemoteAddr { 49 | type Iter = std::option::IntoIter; 50 | fn to_socket_addrs(&self) -> io::Result { 51 | match self { 52 | RemoteAddr::Socket(addr) => addr.to_socket_addrs(), 53 | RemoteAddr::Str(_) => Err(io::Error::new( 54 | io::ErrorKind::InvalidInput, 55 | "The RemoteAddr is not a SocketAddr", 56 | )), 57 | } 58 | } 59 | } 60 | 61 | impl std::fmt::Display for RemoteAddr { 62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 63 | match self { 64 | RemoteAddr::Socket(addr) => write!(f, "{addr}"), 65 | RemoteAddr::Str(string) => write!(f, "{string}"), 66 | } 67 | } 68 | } 69 | 70 | /// Similar to [`ToSocketAddrs`] but for a `RemoteAddr`. 71 | /// Instead of `ToSocketAddrs` that only can accept valid 'ip:port' string format, 72 | /// `ToRemoteAddr` accept any string without panic. 73 | /// If the string has the 'ip:port' format, it will be interpreted as a [`SocketAddr`], 74 | /// if not, it will be interpreted as a string. 75 | pub trait ToRemoteAddr { 76 | fn to_remote_addr(&self) -> io::Result; 77 | } 78 | 79 | impl ToRemoteAddr for &str { 80 | fn to_remote_addr(&self) -> io::Result { 81 | Ok(match self.parse() { 82 | Ok(addr) => RemoteAddr::Socket(addr), 83 | Err(_) => RemoteAddr::Str(self.to_string()), 84 | }) 85 | } 86 | } 87 | 88 | impl ToRemoteAddr for String { 89 | fn to_remote_addr(&self) -> io::Result { 90 | (self as &str).to_remote_addr() 91 | } 92 | } 93 | 94 | impl ToRemoteAddr for &String { 95 | fn to_remote_addr(&self) -> io::Result { 96 | (self as &str).to_remote_addr() 97 | } 98 | } 99 | 100 | impl ToRemoteAddr for SocketAddr { 101 | fn to_remote_addr(&self) -> io::Result { 102 | Ok(RemoteAddr::Socket(*self)) 103 | } 104 | } 105 | 106 | impl ToRemoteAddr for SocketAddrV4 { 107 | fn to_remote_addr(&self) -> io::Result { 108 | Ok(RemoteAddr::Socket(SocketAddr::V4(*self))) 109 | } 110 | } 111 | 112 | impl ToRemoteAddr for SocketAddrV6 { 113 | fn to_remote_addr(&self) -> io::Result { 114 | Ok(RemoteAddr::Socket(SocketAddr::V6(*self))) 115 | } 116 | } 117 | 118 | impl ToRemoteAddr for RemoteAddr { 119 | fn to_remote_addr(&self) -> io::Result { 120 | Ok(self.clone()) 121 | } 122 | } 123 | 124 | impl ToRemoteAddr for (&str, u16) { 125 | fn to_remote_addr(&self) -> io::Result { 126 | Ok(RemoteAddr::Socket(self.to_socket_addrs().unwrap().next().unwrap())) 127 | } 128 | } 129 | 130 | impl ToRemoteAddr for (String, u16) { 131 | fn to_remote_addr(&self) -> io::Result { 132 | Ok(RemoteAddr::Socket(self.to_socket_addrs().unwrap().next().unwrap())) 133 | } 134 | } 135 | 136 | impl ToRemoteAddr for (IpAddr, u16) { 137 | fn to_remote_addr(&self) -> io::Result { 138 | Ok(RemoteAddr::Socket(self.to_socket_addrs().unwrap().next().unwrap())) 139 | } 140 | } 141 | 142 | impl ToRemoteAddr for (Ipv4Addr, u16) { 143 | fn to_remote_addr(&self) -> io::Result { 144 | Ok(RemoteAddr::Socket(self.to_socket_addrs().unwrap().next().unwrap())) 145 | } 146 | } 147 | 148 | impl ToRemoteAddr for (Ipv6Addr, u16) { 149 | fn to_remote_addr(&self) -> io::Result { 150 | Ok(RemoteAddr::Socket(self.to_socket_addrs().unwrap().next().unwrap())) 151 | } 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | use super::*; 157 | use std::net::{IpAddr, Ipv4Addr}; 158 | 159 | #[test] 160 | fn str_to_string() { 161 | let string = "ws://domain:1234/socket"; 162 | assert_eq!(string, string.to_remote_addr().unwrap().string()); 163 | } 164 | 165 | #[test] 166 | fn string_to_string() { 167 | let string = String::from("ws://domain:1234/socket"); 168 | assert_eq!(&string, string.to_remote_addr().unwrap().string()); 169 | } 170 | 171 | #[test] 172 | fn str_to_socket_addr() { 173 | assert!("127.0.0.1:80".to_remote_addr().unwrap().is_socket_addr()); 174 | } 175 | 176 | #[test] 177 | fn string_to_socket_addr() { 178 | assert!(String::from("127.0.0.1:80").to_remote_addr().unwrap().is_socket_addr()); 179 | } 180 | 181 | #[test] 182 | fn socket_addr_to_socket_addr() { 183 | let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); 184 | assert!(socket_addr.to_remote_addr().unwrap().is_socket_addr()); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/network/resource_id.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{Ordering, AtomicUsize}, 3 | }; 4 | 5 | /// Information about the type of resource 6 | #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] 7 | pub enum ResourceType { 8 | Local, 9 | Remote, 10 | } 11 | 12 | /// Unique identifier of a network resource in your system. 13 | /// The identifier wrap 3 values, 14 | /// - The type, that can be a value of [ResourceType]. 15 | /// - The adapter id, that represents the adapter that creates this id 16 | /// - The base value: that is an unique identifier of the resource inside of its adapter. 17 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 18 | pub struct ResourceId { 19 | id: usize, 20 | } 21 | 22 | impl ResourceId { 23 | const ADAPTER_ID_POS: usize = 0; 24 | const RESOURCE_TYPE_POS: usize = 7; 25 | const BASE_VALUE_POS: usize = 8; 26 | 27 | const ADAPTER_ID_MASK: u8 = 0b01111111; // 5 bits 28 | const BASE_VALUE_MASK: usize = 0xFFFFFFFFFFFFFF00_u64 as usize; // 7 bytes 29 | 30 | pub const MAX_BASE_VALUE: usize = (Self::BASE_VALUE_MASK >> Self::BASE_VALUE_POS); 31 | pub const MAX_ADAPTER_ID: u8 = (Self::ADAPTER_ID_MASK >> Self::ADAPTER_ID_POS); 32 | pub const MAX_ADAPTERS: usize = Self::MAX_ADAPTER_ID as usize + 1; 33 | 34 | fn new(adapter_id: u8, resource_type: ResourceType, base_value: usize) -> Self { 35 | debug_assert!( 36 | adapter_id <= Self::MAX_ADAPTER_ID, 37 | "The adapter_id must be less than {}", 38 | Self::MAX_ADAPTER_ID + 1, 39 | ); 40 | 41 | debug_assert!( 42 | base_value <= Self::MAX_BASE_VALUE, 43 | "The base_value must be less than {}", 44 | Self::MAX_BASE_VALUE + 1, 45 | ); 46 | 47 | let resource_type = match resource_type { 48 | ResourceType::Local => 1 << Self::RESOURCE_TYPE_POS, 49 | ResourceType::Remote => 0, 50 | }; 51 | 52 | Self { 53 | id: ((adapter_id as usize) << Self::ADAPTER_ID_POS) 54 | | resource_type 55 | | (base_value << Self::BASE_VALUE_POS), 56 | } 57 | } 58 | 59 | /// Returns the internal representation of this id 60 | pub fn raw(&self) -> usize { 61 | self.id 62 | } 63 | 64 | /// Returns the [ResourceType] of this resource 65 | pub fn resource_type(&self) -> ResourceType { 66 | if self.id & (1 << Self::RESOURCE_TYPE_POS) != 0 { 67 | ResourceType::Local 68 | } 69 | else { 70 | ResourceType::Remote 71 | } 72 | } 73 | 74 | /// Tells if the id preresents a local resource. 75 | pub fn is_local(&self) -> bool { 76 | self.resource_type() == ResourceType::Local 77 | } 78 | 79 | /// Tells if the id preresents a remote resource. 80 | pub fn is_remote(&self) -> bool { 81 | self.resource_type() == ResourceType::Remote 82 | } 83 | 84 | /// Returns the associated adapter id. 85 | /// Note that this returned value is the same as the value of [`crate::network::Transport::id()`] 86 | /// if that transport uses the same adapter. 87 | pub fn adapter_id(&self) -> u8 { 88 | ((self.id & Self::ADAPTER_ID_MASK as usize) >> Self::ADAPTER_ID_POS) as u8 89 | } 90 | 91 | /// Returns the unique resource identifier inside the associated adapter. 92 | pub fn base_value(&self) -> usize { 93 | (self.id & Self::BASE_VALUE_MASK) >> Self::BASE_VALUE_POS 94 | } 95 | } 96 | 97 | impl From for ResourceId { 98 | fn from(raw: usize) -> Self { 99 | Self { id: raw } 100 | } 101 | } 102 | 103 | impl std::fmt::Display for ResourceId { 104 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 105 | let resource_type = match self.resource_type() { 106 | ResourceType::Local => "L", 107 | ResourceType::Remote => "R", 108 | }; 109 | write!(f, "[{}.{}.{}]", self.adapter_id(), resource_type, self.base_value()) 110 | } 111 | } 112 | 113 | impl std::fmt::Debug for ResourceId { 114 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 115 | write!(f, "{self}") 116 | } 117 | } 118 | 119 | /// Used by the adapters in order to create unique ids for their resources. 120 | pub struct ResourceIdGenerator { 121 | last: AtomicUsize, 122 | adapter_id: u8, 123 | resource_type: ResourceType, 124 | } 125 | 126 | impl ResourceIdGenerator { 127 | pub fn new(adapter_id: u8, resource_type: ResourceType) -> Self { 128 | Self { last: AtomicUsize::new(0), adapter_id, resource_type } 129 | } 130 | 131 | /// Generates a new id. 132 | /// This id will contain information about the [`ResourceType`] and the associated adapter. 133 | pub fn generate(&self) -> ResourceId { 134 | let last = self.last.fetch_add(1, Ordering::SeqCst); 135 | ResourceId::new(self.adapter_id, self.resource_type, last) 136 | } 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use super::*; 142 | 143 | #[test] 144 | fn base_value() { 145 | let low_base_value = 0; 146 | 147 | let resource_id = ResourceId::new(1, ResourceType::Local, low_base_value); 148 | assert_eq!(low_base_value, resource_id.base_value()); 149 | 150 | let high_base_value = ResourceId::MAX_BASE_VALUE; 151 | 152 | let resource_id = ResourceId::new(1, ResourceType::Local, high_base_value); 153 | assert_eq!(high_base_value, resource_id.base_value()); 154 | } 155 | 156 | #[test] 157 | fn resource_type() { 158 | let resource_id = ResourceId::new(0, ResourceType::Local, 0); 159 | assert_eq!(ResourceType::Local, resource_id.resource_type()); 160 | assert_eq!(0, resource_id.adapter_id()); 161 | 162 | let resource_id = ResourceId::new(0, ResourceType::Remote, 0); 163 | assert_eq!(ResourceType::Remote, resource_id.resource_type()); 164 | assert_eq!(0, resource_id.adapter_id()); 165 | } 166 | 167 | #[test] 168 | fn adapter_id() { 169 | let adapter_id = ResourceId::MAX_ADAPTER_ID; 170 | 171 | let resource_id = ResourceId::new(adapter_id, ResourceType::Local, 0); 172 | assert_eq!(adapter_id, resource_id.adapter_id()); 173 | assert_eq!(ResourceType::Local, resource_id.resource_type()); 174 | 175 | let resource_id = ResourceId::new(adapter_id, ResourceType::Remote, 0); 176 | assert_eq!(adapter_id, resource_id.adapter_id()); 177 | assert_eq!(ResourceType::Remote, resource_id.resource_type()); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/adapters/framed_tcp.rs: -------------------------------------------------------------------------------- 1 | pub use socket2::{TcpKeepalive}; 2 | 3 | use crate::network::adapter::{ 4 | Resource, Remote, Local, Adapter, SendStatus, AcceptedType, ReadStatus, ConnectionInfo, 5 | ListeningInfo, PendingStatus, 6 | }; 7 | use crate::network::{RemoteAddr, Readiness, TransportConnect, TransportListen}; 8 | use crate::util::encoding::{self, Decoder, MAX_ENCODED_SIZE}; 9 | 10 | use mio::net::{TcpListener, TcpStream}; 11 | use mio::event::{Source}; 12 | 13 | use socket2::{Socket}; 14 | 15 | use std::net::{SocketAddr}; 16 | use std::io::{self, ErrorKind, Read, Write}; 17 | use std::cell::{RefCell}; 18 | use std::mem::{forget, MaybeUninit}; 19 | #[cfg(target_os = "windows")] 20 | use std::os::windows::io::{FromRawSocket, AsRawSocket}; 21 | #[cfg(not(target_os = "windows"))] 22 | use std::os::{fd::AsRawFd, unix::io::FromRawFd}; 23 | 24 | const INPUT_BUFFER_SIZE: usize = u16::MAX as usize; // 2^16 - 1 25 | 26 | #[derive(Clone, Debug, Default)] 27 | pub struct FramedTcpConnectConfig { 28 | keepalive: Option, 29 | } 30 | 31 | impl FramedTcpConnectConfig { 32 | /// Enables TCP keepalive settings on the socket. 33 | pub fn with_keepalive(mut self, keepalive: TcpKeepalive) -> Self { 34 | self.keepalive = Some(keepalive); 35 | self 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug, Default)] 40 | pub struct FramedTcpListenConfig { 41 | keepalive: Option, 42 | } 43 | 44 | impl FramedTcpListenConfig { 45 | /// Enables TCP keepalive settings on client connection sockets. 46 | pub fn with_keepalive(mut self, keepalive: TcpKeepalive) -> Self { 47 | self.keepalive = Some(keepalive); 48 | self 49 | } 50 | } 51 | 52 | pub(crate) struct FramedTcpAdapter; 53 | impl Adapter for FramedTcpAdapter { 54 | type Remote = RemoteResource; 55 | type Local = LocalResource; 56 | } 57 | 58 | pub(crate) struct RemoteResource { 59 | stream: TcpStream, 60 | decoder: RefCell, 61 | keepalive: Option, 62 | } 63 | 64 | // SAFETY: 65 | // That RefCell can be used with Sync because the decoder is only used in the read_event, 66 | // that will be called always from the same thread. This way, we save the cost of a Mutex. 67 | unsafe impl Sync for RemoteResource {} 68 | 69 | impl RemoteResource { 70 | fn new(stream: TcpStream, keepalive: Option) -> Self { 71 | Self { stream, decoder: RefCell::new(Decoder::default()), keepalive } 72 | } 73 | } 74 | 75 | impl Resource for RemoteResource { 76 | fn source(&mut self) -> &mut dyn Source { 77 | &mut self.stream 78 | } 79 | } 80 | 81 | impl Remote for RemoteResource { 82 | fn connect_with( 83 | config: TransportConnect, 84 | remote_addr: RemoteAddr, 85 | ) -> io::Result> { 86 | let config = match config { 87 | TransportConnect::FramedTcp(config) => config, 88 | _ => panic!("Internal error: Got wrong config"), 89 | }; 90 | let peer_addr = *remote_addr.socket_addr(); 91 | let stream = TcpStream::connect(peer_addr)?; 92 | let local_addr = stream.local_addr()?; 93 | Ok(ConnectionInfo { 94 | remote: RemoteResource::new(stream, config.keepalive), 95 | local_addr, 96 | peer_addr, 97 | }) 98 | } 99 | 100 | fn receive(&self, mut process_data: impl FnMut(&[u8])) -> ReadStatus { 101 | let buffer: MaybeUninit<[u8; INPUT_BUFFER_SIZE]> = MaybeUninit::uninit(); 102 | let mut input_buffer = unsafe { buffer.assume_init() }; // Avoid to initialize the array 103 | 104 | loop { 105 | let mut stream = &self.stream; 106 | match stream.read(&mut input_buffer) { 107 | Ok(0) => break ReadStatus::Disconnected, 108 | Ok(size) => { 109 | let data = &input_buffer[..size]; 110 | log::trace!("Decoding {} bytes", data.len()); 111 | self.decoder.borrow_mut().decode(data, |decoded_data| { 112 | process_data(decoded_data); 113 | }); 114 | } 115 | Err(ref err) if err.kind() == ErrorKind::Interrupted => continue, 116 | Err(ref err) if err.kind() == ErrorKind::WouldBlock => { 117 | break ReadStatus::WaitNextEvent 118 | } 119 | Err(ref err) if err.kind() == ErrorKind::ConnectionReset => { 120 | break ReadStatus::Disconnected 121 | } 122 | Err(err) => { 123 | log::error!("TCP receive error: {}", err); 124 | break ReadStatus::Disconnected; // should not happen 125 | } 126 | } 127 | } 128 | } 129 | 130 | fn send(&self, data: &[u8]) -> SendStatus { 131 | let mut buf = [0; MAX_ENCODED_SIZE]; // used to avoid a heap allocation 132 | let encoded_size = encoding::encode_size(data, &mut buf); 133 | 134 | let mut total_bytes_sent = 0; 135 | let total_bytes = encoded_size.len() + data.len(); 136 | loop { 137 | let data_to_send = match total_bytes_sent < encoded_size.len() { 138 | true => &encoded_size[total_bytes_sent..], 139 | false => &data[total_bytes_sent - encoded_size.len()..], 140 | }; 141 | 142 | let mut stream = &self.stream; 143 | match stream.write(data_to_send) { 144 | Ok(bytes_sent) => { 145 | total_bytes_sent += bytes_sent; 146 | if total_bytes_sent == total_bytes { 147 | break SendStatus::Sent; 148 | } 149 | } 150 | Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => continue, 151 | Err(err) => { 152 | log::error!("TCP receive error: {}", err); 153 | break SendStatus::ResourceNotFound; // should not happen 154 | } 155 | } 156 | } 157 | } 158 | 159 | fn pending(&self, _readiness: Readiness) -> PendingStatus { 160 | let status = super::tcp::check_stream_ready(&self.stream); 161 | 162 | if status == PendingStatus::Ready { 163 | if let Some(keepalive) = &self.keepalive { 164 | #[cfg(target_os = "windows")] 165 | let socket = unsafe { Socket::from_raw_socket(self.stream.as_raw_socket()) }; 166 | #[cfg(not(target_os = "windows"))] 167 | let socket = unsafe { Socket::from_raw_fd(self.stream.as_raw_fd()) }; 168 | 169 | if let Err(e) = socket.set_tcp_keepalive(keepalive) { 170 | log::warn!("TCP set keepalive error: {}", e); 171 | } 172 | 173 | // Don't drop so the underlying socket is not closed. 174 | forget(socket); 175 | } 176 | } 177 | 178 | status 179 | } 180 | } 181 | 182 | pub(crate) struct LocalResource { 183 | listener: TcpListener, 184 | keepalive: Option, 185 | } 186 | 187 | impl Resource for LocalResource { 188 | fn source(&mut self) -> &mut dyn Source { 189 | &mut self.listener 190 | } 191 | } 192 | 193 | impl Local for LocalResource { 194 | type Remote = RemoteResource; 195 | 196 | fn listen_with(config: TransportListen, addr: SocketAddr) -> io::Result> { 197 | let config = match config { 198 | TransportListen::FramedTcp(config) => config, 199 | _ => panic!("Internal error: Got wrong config"), 200 | }; 201 | let listener = TcpListener::bind(addr)?; 202 | let local_addr = listener.local_addr().unwrap(); 203 | Ok(ListeningInfo { 204 | local: { LocalResource { listener, keepalive: config.keepalive } }, 205 | local_addr, 206 | }) 207 | } 208 | 209 | fn accept(&self, mut accept_remote: impl FnMut(AcceptedType<'_, Self::Remote>)) { 210 | loop { 211 | match self.listener.accept() { 212 | Ok((stream, addr)) => accept_remote(AcceptedType::Remote( 213 | addr, 214 | RemoteResource::new(stream, self.keepalive.clone()), 215 | )), 216 | Err(ref err) if err.kind() == ErrorKind::WouldBlock => break, 217 | Err(ref err) if err.kind() == ErrorKind::Interrupted => continue, 218 | Err(err) => break log::error!("TCP accept error: {}", err), // Should not happen 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/network/transport.rs: -------------------------------------------------------------------------------- 1 | use super::loader::{DriverLoader}; 2 | 3 | #[cfg(feature = "tcp")] 4 | use crate::adapters::tcp::{TcpAdapter, TcpConnectConfig, TcpListenConfig}; 5 | #[cfg(feature = "tcp")] 6 | use crate::adapters::framed_tcp::{FramedTcpAdapter, FramedTcpConnectConfig, FramedTcpListenConfig}; 7 | #[cfg(feature = "udp")] 8 | use crate::adapters::udp::{self, UdpAdapter, UdpConnectConfig, UdpListenConfig}; 9 | #[cfg(feature = "websocket")] 10 | use crate::adapters::ws::{self, WsAdapter}; 11 | 12 | use serde::{Serialize, Deserialize}; 13 | 14 | /// Enum to identified the underlying transport used. 15 | /// It can be passed to 16 | /// [`NetworkController::connect()`](crate::network::NetworkController::connect()) and 17 | /// [`NetworkController::listen()`](crate::network::NetworkController::connect()) methods 18 | /// to specify the transport used. 19 | #[derive(strum::EnumIter, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, Debug)] 20 | pub enum Transport { 21 | /// TCP protocol (available through the *tcp* feature). 22 | /// As stream protocol, receiving a message from TCP do not imply to read 23 | /// the entire message. 24 | /// If you want a packet based way to send over TCP, use `FramedTcp` instead. 25 | #[cfg(feature = "tcp")] 26 | Tcp, 27 | 28 | /// Tcp framed protocol (available through the *tcp* feature). 29 | /// Like TCP, but encoded with a slim frame layer to manage the data as a packet, 30 | /// instead of as a stream. 31 | /// It prefixes the message using variable integer encoding with the size of the message. 32 | /// Most of the time you would want to use this instead of the raw `Tcp`. 33 | #[cfg(feature = "tcp")] 34 | FramedTcp, 35 | 36 | /// UDP protocol (available through the *udp* feature). 37 | /// Take into account that UDP is not connection oriented and a packet can be lost 38 | /// or received disordered. 39 | /// If it is specified in the listener and the address is a Ipv4 in the range of multicast ips 40 | /// (from `224.0.0.0` to `239.255.255.255`), the listener will be configured in multicast mode. 41 | #[cfg(feature = "udp")] 42 | Udp, 43 | 44 | /// WebSocket protocol (available through the *websocket* feature). 45 | /// If you use a [`crate::network::RemoteAddr::Str`] in the `connect()` method, 46 | /// you can specify an URL with `wss` of `ws` schemas to connect with or without security. 47 | /// If you use a [`crate::network::RemoteAddr::Socket`] the socket will be a normal 48 | /// websocket with the following uri: `ws://{SocketAddr}/message-io-default`. 49 | #[cfg(feature = "websocket")] 50 | Ws, 51 | } 52 | 53 | impl Transport { 54 | /// Associates an adapter. 55 | /// This method mounts the adapters to be used in the network instance. 56 | pub fn mount_adapter(self, loader: &mut DriverLoader) { 57 | match self { 58 | #[cfg(feature = "tcp")] 59 | Self::Tcp => loader.mount(self.id(), TcpAdapter), 60 | #[cfg(feature = "tcp")] 61 | Self::FramedTcp => loader.mount(self.id(), FramedTcpAdapter), 62 | #[cfg(feature = "udp")] 63 | Self::Udp => loader.mount(self.id(), UdpAdapter), 64 | #[cfg(feature = "websocket")] 65 | Self::Ws => loader.mount(self.id(), WsAdapter), 66 | }; 67 | } 68 | 69 | /// Maximum theorical packet payload length available for each transport. 70 | /// 71 | /// The value returned by this function is the **theorical maximum** and could not be valid for 72 | /// all networks. 73 | /// You can ensure your message not exceeds `udp::MAX_INTERNET_PAYLOAD_LEN` in order to be 74 | /// more cross-platform compatible. 75 | pub const fn max_message_size(self) -> usize { 76 | match self { 77 | #[cfg(feature = "tcp")] 78 | Self::Tcp => usize::MAX, 79 | #[cfg(feature = "tcp")] 80 | Self::FramedTcp => usize::MAX, 81 | #[cfg(feature = "udp")] 82 | Self::Udp => udp::MAX_LOCAL_PAYLOAD_LEN, 83 | #[cfg(feature = "websocket")] 84 | Self::Ws => ws::MAX_PAYLOAD_LEN, 85 | } 86 | } 87 | 88 | /// Tell if the transport protocol is a connection oriented protocol. 89 | /// If it is, `Connection` and `Disconnection` events will be generated. 90 | pub const fn is_connection_oriented(self) -> bool { 91 | match self { 92 | #[cfg(feature = "tcp")] 93 | Transport::Tcp => true, 94 | #[cfg(feature = "tcp")] 95 | Transport::FramedTcp => true, 96 | #[cfg(feature = "udp")] 97 | Transport::Udp => false, 98 | #[cfg(feature = "websocket")] 99 | Transport::Ws => true, 100 | } 101 | } 102 | 103 | /// Tell if the transport protocol is a packet-based protocol. 104 | /// It implies that any send call corresponds to a data message event. 105 | /// It satisfies that the number of bytes sent are the same as received. 106 | /// The opossite of a packet-based is a stream-based transport (e.g Tcp). 107 | /// In this case, reading a data message event do not imply reading the entire message sent. 108 | /// It is in change of the user to determinate how to read the data. 109 | pub const fn is_packet_based(self) -> bool { 110 | match self { 111 | #[cfg(feature = "tcp")] 112 | Transport::Tcp => false, 113 | #[cfg(feature = "tcp")] 114 | Transport::FramedTcp => true, 115 | #[cfg(feature = "udp")] 116 | Transport::Udp => true, 117 | #[cfg(feature = "websocket")] 118 | Transport::Ws => true, 119 | } 120 | } 121 | 122 | /// Returns the adapter id used for this transport. 123 | /// It is equivalent to the position of the enum starting by 0 124 | pub const fn id(self) -> u8 { 125 | match self { 126 | #[cfg(feature = "tcp")] 127 | Transport::Tcp => 0, 128 | #[cfg(feature = "tcp")] 129 | Transport::FramedTcp => 1, 130 | #[cfg(feature = "udp")] 131 | Transport::Udp => 2, 132 | #[cfg(feature = "websocket")] 133 | Transport::Ws => 3, 134 | } 135 | } 136 | } 137 | 138 | impl From for Transport { 139 | fn from(id: u8) -> Self { 140 | match id { 141 | #[cfg(feature = "tcp")] 142 | 0 => Transport::Tcp, 143 | #[cfg(feature = "tcp")] 144 | 1 => Transport::FramedTcp, 145 | #[cfg(feature = "udp")] 146 | 2 => Transport::Udp, 147 | #[cfg(feature = "websocket")] 148 | 3 => Transport::Ws, 149 | _ => panic!("Not available transport"), 150 | } 151 | } 152 | } 153 | 154 | impl std::fmt::Display for Transport { 155 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 156 | write!(f, "{self:?}") 157 | } 158 | } 159 | 160 | #[derive(Debug)] 161 | pub enum TransportConnect { 162 | #[cfg(feature = "tcp")] 163 | Tcp(TcpConnectConfig), 164 | #[cfg(feature = "tcp")] 165 | FramedTcp(FramedTcpConnectConfig), 166 | #[cfg(feature = "udp")] 167 | Udp(UdpConnectConfig), 168 | #[cfg(feature = "websocket")] 169 | Ws, 170 | } 171 | 172 | impl TransportConnect { 173 | pub fn id(&self) -> u8 { 174 | let transport = match self { 175 | #[cfg(feature = "tcp")] 176 | Self::Tcp(_) => Transport::Tcp, 177 | #[cfg(feature = "tcp")] 178 | Self::FramedTcp(_) => Transport::FramedTcp, 179 | #[cfg(feature = "udp")] 180 | Self::Udp(_) => Transport::Udp, 181 | #[cfg(feature = "websocket")] 182 | Self::Ws => Transport::Ws, 183 | }; 184 | 185 | transport.id() 186 | } 187 | } 188 | 189 | impl From for TransportConnect { 190 | fn from(transport: Transport) -> Self { 191 | match transport { 192 | #[cfg(feature = "tcp")] 193 | Transport::Tcp => Self::Tcp(TcpConnectConfig::default()), 194 | #[cfg(feature = "tcp")] 195 | Transport::FramedTcp => Self::FramedTcp(FramedTcpConnectConfig::default()), 196 | #[cfg(feature = "udp")] 197 | Transport::Udp => Self::Udp(UdpConnectConfig::default()), 198 | #[cfg(feature = "websocket")] 199 | Transport::Ws => Self::Ws, 200 | } 201 | } 202 | } 203 | 204 | #[derive(Debug)] 205 | pub enum TransportListen { 206 | #[cfg(feature = "tcp")] 207 | Tcp(TcpListenConfig), 208 | #[cfg(feature = "tcp")] 209 | FramedTcp(FramedTcpListenConfig), 210 | #[cfg(feature = "udp")] 211 | Udp(UdpListenConfig), 212 | #[cfg(feature = "websocket")] 213 | Ws, 214 | } 215 | 216 | impl TransportListen { 217 | pub fn id(&self) -> u8 { 218 | let transport = match self { 219 | #[cfg(feature = "tcp")] 220 | Self::Tcp(_) => Transport::Tcp, 221 | #[cfg(feature = "tcp")] 222 | Self::FramedTcp(_) => Transport::FramedTcp, 223 | #[cfg(feature = "udp")] 224 | Self::Udp(_) => Transport::Udp, 225 | #[cfg(feature = "websocket")] 226 | Self::Ws => Transport::Ws, 227 | }; 228 | 229 | transport.id() 230 | } 231 | } 232 | 233 | impl From for TransportListen { 234 | fn from(transport: Transport) -> Self { 235 | match transport { 236 | #[cfg(feature = "tcp")] 237 | Transport::Tcp => Self::Tcp(TcpListenConfig::default()), 238 | #[cfg(feature = "tcp")] 239 | Transport::FramedTcp => Self::FramedTcp(FramedTcpListenConfig::default()), 240 | #[cfg(feature = "udp")] 241 | Transport::Udp => Self::Udp(UdpListenConfig::default()), 242 | #[cfg(feature = "websocket")] 243 | Transport::Ws => Self::Ws, 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /examples/throughput/main.rs: -------------------------------------------------------------------------------- 1 | use message_io::network::{NetEvent, Transport}; 2 | use message_io::node::{self}; 3 | use message_io::adapters::{udp}; 4 | use message_io::util::encoding::{self, Decoder, MAX_ENCODED_SIZE}; 5 | 6 | use tungstenite::{Message, connect as ws_connect, accept as ws_accept}; 7 | use url::{Url}; 8 | 9 | use std::net::{TcpListener, TcpStream, UdpSocket}; 10 | use std::time::{Duration, Instant}; 11 | use std::sync::{ 12 | Arc, 13 | atomic::{AtomicBool, Ordering}, 14 | mpsc::{self}, 15 | }; 16 | use std::io::{self, Write, Read}; 17 | 18 | const EXPECTED_BYTES: usize = 1024 * 1024 * 1024; // 1GB 19 | 20 | const CHUNK: usize = udp::MAX_LOCAL_PAYLOAD_LEN; 21 | 22 | fn main() { 23 | println!("Sending 1GB in chunks of {} bytes:\n", CHUNK); 24 | throughput_message_io(Transport::Udp, CHUNK); 25 | throughput_message_io(Transport::Tcp, CHUNK); 26 | throughput_message_io(Transport::FramedTcp, CHUNK); 27 | throughput_message_io(Transport::Ws, CHUNK); 28 | println!(); 29 | throughput_native_udp(CHUNK); 30 | throughput_native_tcp(CHUNK); 31 | throughput_native_framed_tcp(CHUNK); 32 | throughput_native_ws(CHUNK); 33 | } 34 | 35 | fn throughput_message_io(transport: Transport, packet_size: usize) { 36 | print!("message-io {}: \t", transport); 37 | io::stdout().flush().unwrap(); 38 | 39 | let (handler, listener) = node::split::<()>(); 40 | let message = (0..packet_size).map(|_| 0xFF).collect::>(); 41 | 42 | let (_, addr) = handler.network().listen(transport, "127.0.0.1:0").unwrap(); 43 | let (endpoint, _) = handler.network().connect(transport, addr).unwrap(); 44 | 45 | let (t_ready, r_ready) = mpsc::channel(); 46 | let (t_time, r_time) = mpsc::channel(); 47 | 48 | let mut task = { 49 | let mut received_bytes = 0; 50 | let handler = handler.clone(); 51 | 52 | listener.for_each_async(move |event| match event.network() { 53 | NetEvent::Connected(_, _) => (), 54 | NetEvent::Accepted(_, _) => t_ready.send(()).unwrap(), 55 | NetEvent::Message(_, data) => { 56 | received_bytes += data.len(); 57 | if received_bytes >= EXPECTED_BYTES { 58 | handler.stop(); 59 | t_time.send(Instant::now()).unwrap(); 60 | } 61 | } 62 | NetEvent::Disconnected(_) => (), 63 | }) 64 | }; 65 | 66 | if transport.is_connection_oriented() { 67 | r_ready.recv().unwrap(); 68 | } 69 | 70 | // Ensure that the connection is performed, 71 | // the internal thread is initialized for not oriented connection protocols 72 | // and we are waiting in the internal poll for data. 73 | std::thread::sleep(Duration::from_millis(100)); 74 | 75 | let start_time = Instant::now(); 76 | while handler.is_running() { 77 | handler.network().send(endpoint, &message); 78 | } 79 | 80 | let end_time = r_time.recv().unwrap(); 81 | let elapsed = end_time - start_time; 82 | println!("Throughput: {}", ThroughputMeasure(EXPECTED_BYTES, elapsed)); 83 | 84 | task.wait(); 85 | } 86 | 87 | fn throughput_native_udp(packet_size: usize) { 88 | print!("native Udp: \t\t"); 89 | io::stdout().flush().unwrap(); 90 | 91 | let receiver = UdpSocket::bind("127.0.0.1:0").unwrap(); 92 | let addr = receiver.local_addr().unwrap(); 93 | 94 | let message = (0..packet_size).map(|_| 0xFF).collect::>(); 95 | let mut buffer: [u8; CHUNK] = [0; CHUNK]; 96 | 97 | let running = Arc::new(AtomicBool::new(true)); 98 | let thread = { 99 | let running = running.clone(); 100 | let message = message.clone(); 101 | std::thread::Builder::new() 102 | .name("sender".into()) 103 | .spawn(move || { 104 | let sender = UdpSocket::bind("127.0.0.1:0").unwrap(); 105 | sender.connect(addr).unwrap(); 106 | let start_time = Instant::now(); 107 | while running.load(Ordering::Relaxed) { 108 | sender.send(&message).unwrap(); 109 | } 110 | start_time 111 | }) 112 | .unwrap() 113 | }; 114 | 115 | let mut total_received = 0; 116 | while total_received < EXPECTED_BYTES { 117 | total_received += receiver.recv(&mut buffer).unwrap(); 118 | } 119 | let end_time = Instant::now(); 120 | running.store(false, Ordering::Relaxed); 121 | 122 | let start_time = thread.join().unwrap(); 123 | let elapsed = end_time - start_time; 124 | println!("Throughput: {}", ThroughputMeasure(EXPECTED_BYTES, elapsed)); 125 | } 126 | 127 | fn throughput_native_tcp(packet_size: usize) { 128 | print!("native Tcp: \t\t"); 129 | io::stdout().flush().unwrap(); 130 | 131 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 132 | let addr = listener.local_addr().unwrap(); 133 | 134 | let message = (0..packet_size).map(|_| 0xFF).collect::>(); 135 | let mut buffer: [u8; CHUNK] = [0; CHUNK]; 136 | 137 | let thread = { 138 | let message = message.clone(); 139 | std::thread::Builder::new() 140 | .name("sender".into()) 141 | .spawn(move || { 142 | let mut total_sent = 0; 143 | let mut sender = TcpStream::connect(addr).unwrap(); 144 | 145 | let start_time = Instant::now(); 146 | while total_sent < EXPECTED_BYTES { 147 | sender.write(&message).unwrap(); 148 | total_sent += message.len(); 149 | } 150 | start_time 151 | }) 152 | .unwrap() 153 | }; 154 | 155 | let (mut receiver, _) = listener.accept().unwrap(); 156 | let mut total_received = 0; 157 | while total_received < EXPECTED_BYTES { 158 | total_received += receiver.read(&mut buffer).unwrap(); 159 | } 160 | let end_time = Instant::now(); 161 | 162 | let start_time = thread.join().unwrap(); 163 | let elapsed = end_time - start_time; 164 | println!("Throughput: {}", ThroughputMeasure(EXPECTED_BYTES, elapsed)); 165 | } 166 | 167 | fn throughput_native_framed_tcp(packet_size: usize) { 168 | print!("native FramedTcp: \t"); 169 | io::stdout().flush().unwrap(); 170 | 171 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 172 | let addr = listener.local_addr().unwrap(); 173 | 174 | let message = (0..packet_size).map(|_| 0xFF).collect::>(); 175 | let mut buffer: [u8; CHUNK] = [0; CHUNK]; 176 | 177 | let thread = { 178 | let message = message.clone(); 179 | std::thread::Builder::new() 180 | .name("sender".into()) 181 | .spawn(move || { 182 | let mut total_sent = 0; 183 | let mut sender = TcpStream::connect(addr).unwrap(); 184 | let mut framming = [0; MAX_ENCODED_SIZE]; // used to avoid a heap allocation 185 | 186 | let start_time = Instant::now(); 187 | while total_sent < EXPECTED_BYTES { 188 | let encoded_size = encoding::encode_size(&message, &mut framming); 189 | sender.write(&encoded_size).unwrap(); 190 | sender.write(&message).unwrap(); 191 | total_sent += message.len(); 192 | } 193 | start_time 194 | }) 195 | .unwrap() 196 | }; 197 | 198 | let (mut receiver, _) = listener.accept().unwrap(); 199 | let mut total_received = 0; 200 | let mut decoder = Decoder::default(); 201 | while total_received < EXPECTED_BYTES { 202 | let mut message_received = false; 203 | while !message_received { 204 | let bytes = receiver.read(&mut buffer).unwrap(); 205 | decoder.decode(&buffer[0..bytes], |decoded_data| { 206 | total_received += decoded_data.len(); 207 | message_received = true; 208 | }); 209 | } 210 | } 211 | let end_time = Instant::now(); 212 | 213 | let start_time = thread.join().unwrap(); 214 | let elapsed = end_time - start_time; 215 | println!("Throughput: {}", ThroughputMeasure(EXPECTED_BYTES, elapsed)); 216 | } 217 | 218 | fn throughput_native_ws(packet_size: usize) { 219 | print!("native Ws: \t\t"); 220 | io::stdout().flush().unwrap(); 221 | 222 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 223 | let addr = listener.local_addr().unwrap(); 224 | 225 | let message = (0..packet_size).map(|_| 0xFF).collect::>(); 226 | 227 | let thread = { 228 | let mut total_sent = 0; 229 | let message = message.clone(); 230 | std::thread::Builder::new() 231 | .name("sender".into()) 232 | .spawn(move || { 233 | let url_addr = format!("ws://{}/socket", addr); 234 | let (mut sender, _) = ws_connect(Url::parse(&url_addr).unwrap()).unwrap(); 235 | let start_time = Instant::now(); 236 | while total_sent < EXPECTED_BYTES { 237 | sender.send(Message::Binary(message.clone().into())).unwrap(); 238 | total_sent += message.len(); 239 | } 240 | start_time 241 | }) 242 | .unwrap() 243 | }; 244 | 245 | let mut receiver = ws_accept(listener.accept().unwrap().0).unwrap(); 246 | let mut total_received = 0; 247 | while total_received < EXPECTED_BYTES { 248 | total_received += receiver.read().unwrap().len(); 249 | } 250 | let end_time = Instant::now(); 251 | 252 | let start_time = thread.join().unwrap(); 253 | let elapsed = end_time - start_time; 254 | println!("Throughput: {}", ThroughputMeasure(EXPECTED_BYTES, elapsed)); 255 | } 256 | 257 | pub struct ThroughputMeasure(usize, Duration); //bytes, elapsed 258 | impl std::fmt::Display for ThroughputMeasure { 259 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 260 | let bytes_per_sec = self.0 as f64 / self.1.as_secs_f64(); 261 | if bytes_per_sec < 1000.0 { 262 | write!(f, "{:.2} B/s", bytes_per_sec) 263 | } 264 | else if bytes_per_sec < 1000_000.0 { 265 | write!(f, "{:.2} KB/s", bytes_per_sec / 1000.0) 266 | } 267 | else if bytes_per_sec < 1000_000_000.0 { 268 | write!(f, "{:.2} MB/s", bytes_per_sec / 1000_000.0) 269 | } 270 | else { 271 | write!(f, "{:.2} GB/s", bytes_per_sec / 1000_000_000.0) 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/network/adapter.rs: -------------------------------------------------------------------------------- 1 | use crate::network::transport::{TransportConnect, TransportListen}; 2 | 3 | use super::remote_addr::{RemoteAddr}; 4 | use super::poll::{Readiness}; 5 | 6 | use mio::event::{Source}; 7 | 8 | use std::net::{SocketAddr}; 9 | use std::io::{self}; 10 | 11 | /// High level trait to represent an adapter for a transport protocol. 12 | /// The adapter is only used to identify the resources of your adapter. 13 | pub trait Adapter: Send + Sync { 14 | /// Resource type used to identify remote connections and send/receive 15 | /// from remote this endpoints (e.g. TcpStream) 16 | /// This can be considerered the resource used for client connections. 17 | type Remote: Remote; 18 | 19 | /// Resource type used to accept new connections (e.g. TcpListener) 20 | /// This can be considerered the resource used for server listenings. 21 | type Local: Local; 22 | } 23 | 24 | /// A `Resource` is defined as an object that can return a mutable reference to a [`Source`]. 25 | /// `Source` is the trait that [`mio`] uses to register in the poll in order to wake up 26 | /// asynchronously from events. 27 | /// Your [`Remote`] and [`Local`] entities must implement `Resource`. 28 | pub trait Resource: Send + Sync { 29 | /// Returns a mutable reference to the internal `Source`. 30 | /// Note: All `mio` network element implements [`Source`], you probably wants to use 31 | /// one of them as a base for your non-blocking transport. 32 | /// See [`Source`]. 33 | fn source(&mut self) -> &mut dyn Source; 34 | } 35 | 36 | /// Plain struct used as a returned value of [`Remote::connect_with()`] 37 | pub struct ConnectionInfo { 38 | /// The new created remote resource 39 | pub remote: R, 40 | 41 | /// Local address of the interal resource used. 42 | pub local_addr: SocketAddr, 43 | 44 | /// Peer address of the interal resource used. 45 | pub peer_addr: SocketAddr, 46 | } 47 | 48 | /// Plain struct used as a returned value of [`Local::listen_with()`] 49 | pub struct ListeningInfo { 50 | /// The new created local resource 51 | pub local: L, 52 | 53 | /// Local address generated after perform the listening action. 54 | pub local_addr: SocketAddr, 55 | } 56 | 57 | /// The following represents the posible status that [`crate::network::NetworkController::send()`] 58 | /// call can return. 59 | /// The library do not encourage to perform the check of this status for each `send()` call, 60 | /// only in that cases where you need extra information about how the sending method was. 61 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 62 | pub enum SendStatus { 63 | /// This status is received when the entire data has been sent. 64 | /// It does not guarantees that the packet have been successfully received by the endpoint. 65 | /// It means that the correspond adapter has sent the message to the OS without errors. 66 | Sent, 67 | 68 | /// This status is received in packet-based protocols where there is a limit in the bytes 69 | /// that a packet can have. 70 | MaxPacketSizeExceeded, 71 | 72 | /// It means that the message could not be sent by the specified `ResourceId`. 73 | /// This implies that a [`crate::network::NetEvent::Disconnected`] has happened or that 74 | /// the resource never existed. 75 | ResourceNotFound, 76 | 77 | /// The resource can not perform the required send operation. 78 | /// Usually this is due because it is performing the handshake. 79 | ResourceNotAvailable, 80 | } 81 | 82 | /// Returned as a result of [`Remote::receive()`] 83 | #[derive(Debug)] 84 | pub enum ReadStatus { 85 | /// This status must be returned if the resource has been disconnected or there was an error. 86 | /// The resource will be removed after this call and 87 | /// no more [`Remote::receive()`] calls will be produced by this resource. 88 | Disconnected, 89 | 90 | /// This status must be returned when a the resource (treated as a non-bloking) would wait for 91 | /// process the next event. 92 | /// Usually, this status is returned if the resource receives 93 | /// a [`std::io::ErrorKind::WouldBlock`]. 94 | WaitNextEvent, 95 | } 96 | 97 | #[derive(Debug, Clone, PartialEq, Eq)] 98 | pub enum PendingStatus { 99 | /// The resource is no longer considered as a pending resource. 100 | /// It it came from a listener, a [`crate::network::NetEvent::Accepted`] event will be generated. 101 | /// It it came from a explicit connection, a [`crate::network::NetEvent::Connected`] 102 | /// with its flag to `true` will be generated. 103 | /// No more calls to [`Remote::pending()`] will be performed. 104 | Ready, 105 | 106 | /// The resource needs more data to be considered as established. 107 | Incomplete, 108 | 109 | /// The resource has not be able to perform the connection. 110 | /// It it came from a listener, no event will be generated. 111 | /// It it came from a explicit connection, a [`crate::network::NetEvent::Connected`] 112 | /// with its flag to `false` will be generated. 113 | /// No more calls to [`Remote::pending()`] will be performed and the resource will be removed. 114 | Disconnected, 115 | } 116 | 117 | /// The resource used to represent a remote. 118 | /// It usually is a wrapper over a socket/stream. 119 | pub trait Remote: Resource + Sized { 120 | /// Called when the user performs a connection request to an specific remote address. 121 | /// The **implementator** is in change of creating the corresponding remote resource. 122 | /// The [`TransportConnect`] wraps custom transport options for transports that support it. It 123 | /// is guaranteed by the upper level to be of the variant matching the adapter. Therefore other 124 | /// variants can be safely ignored. 125 | /// The [`RemoteAddr`] contains either a [`SocketAddr`] or a [`url::Url`]. 126 | /// It is in charge of deciding what to do in both cases. 127 | /// It also must return the extracted address as `SocketAddr`. 128 | fn connect_with( 129 | config: TransportConnect, 130 | remote_addr: RemoteAddr, 131 | ) -> io::Result>; 132 | 133 | /// Called when a remote resource received an event. 134 | /// The resource must be *ready* to receive this call. 135 | /// It means that it has available data to read, 136 | /// or there is some connection related issue, as a disconnection. 137 | /// The **implementator** is in charge of processing that action and returns a [`ReadStatus`]. 138 | /// 139 | /// The `process_data` function must be called for each data chunk that represents a message. 140 | /// This call will produce a [`crate::network::NetEvent::Message`] API event. 141 | /// Note that `receive()` could imply more than one call to `read`. 142 | /// The implementator must be read all data from the resource. 143 | /// For most of the cases it means read until the network resource returns `WouldBlock`. 144 | fn receive(&self, process_data: impl FnMut(&[u8])) -> ReadStatus; 145 | 146 | /// Sends raw data from a resource. 147 | /// The resource must be *ready* to receive this call. 148 | /// The **implementator** is in charge to send the entire `data`. 149 | /// The [`SendStatus`] will contain the status of this attempt. 150 | fn send(&self, data: &[u8]) -> SendStatus; 151 | 152 | /// Called when a `Remote` is created (explicity of by a listener) 153 | /// and it is not consider ready yet. 154 | /// A remote resource **is considered ready** when it is totally connected 155 | /// and can be used for writing data. 156 | /// It implies that the user has received the `Connected` or `Accepted` method for that resource. 157 | /// 158 | /// This method is in charge to determine if a resource is ready or not. 159 | /// No `Connected` or `Accepted` events will be generated until this function return 160 | /// `PendingStatus::Ready`. 161 | /// The method wil be called several times with different `Readiness` until the **implementator** 162 | /// returns a `PendingStatus::Ready` or `PendingStatus::Disconnected`. 163 | fn pending(&self, readiness: Readiness) -> PendingStatus; 164 | 165 | /// The resource is available to write. 166 | /// It must be *ready* to receive this call. 167 | /// Here the **implementator** optionally can try to write any pending data. 168 | /// The return value is an identification of the operation result. 169 | /// If the method returns `true`, the operation was successful, otherwise, the resource will 170 | /// be disconnected and removed. 171 | fn ready_to_write(&self) -> bool { 172 | true 173 | } 174 | } 175 | 176 | /// Used as a parameter callback in [`Local::accept()`] 177 | pub enum AcceptedType<'a, R> { 178 | /// The listener has accepted a remote (`R`) with the specified addr. 179 | /// The remote will be registered in order to generate read events. (calls to 180 | /// [`Remote::receive()`]). 181 | /// A [`crate::network::NetEvent::Accepted`] will be generated once this remote resource 182 | /// is considered *ready*. 183 | Remote(SocketAddr, R), 184 | 185 | /// The listener has accepted data that can be packed into a message from a specified addr. 186 | /// Despite of `Remote`, accept as a `Data` will not register any Remote. 187 | /// This will produce a [`crate::network::NetEvent::Message`] event. 188 | /// The endpoint of this event will be unique containing the specified addr and the listener 189 | /// whom generates it. 190 | Data(SocketAddr, &'a [u8]), 191 | } 192 | 193 | /// The resource used to represent a local listener. 194 | /// It usually is a wrapper over a socket/listener. 195 | pub trait Local: Resource + Sized { 196 | /// The type of the Remote accepted by the [`Self::accept()`] function. 197 | /// It must be the same as the adapter's `Remote`. 198 | type Remote: Remote; 199 | 200 | /// Called when the user performs a listening request from an specific address. 201 | /// The **implementator** is in change of creating the corresponding local resource. 202 | /// It also must returned the listening address since it could not be the same as param `addr` 203 | /// (e.g. listening from port `0`). 204 | /// The [`TransportListen`] wraps custom transport options for transports that support it. It 205 | /// is guaranteed by the upper level to be of the variant matching the adapter. Therefore other 206 | /// variants can be safely ignored. 207 | fn listen_with(config: TransportListen, addr: SocketAddr) -> io::Result>; 208 | 209 | /// Called when a local resource received an event. 210 | /// It means that some resource have tried to connect. 211 | /// The **implementator** is in charge of accepting this connection. 212 | /// The `accept_remote` must be called for each accept request in the local resource. 213 | /// Note that an accept event could imply to process more than one remote. 214 | /// This function is called when the local resource has one or more pending connections. 215 | /// The **implementator** must process all these pending connections in this call. 216 | /// For most of the cases it means accept connections until the network 217 | /// resource returns `WouldBlock`. 218 | fn accept(&self, accept_remote: impl FnMut(AcceptedType<'_, Self::Remote>)); 219 | 220 | /// Sends a raw data from a resource. 221 | /// Similar to [`Remote::send()`] but the resource that sends the data is a `Local`. 222 | /// This behaviour usually happens when the transport to implement is not connection oriented. 223 | /// 224 | /// The **implementator** must **only** implement this function if the local resource can 225 | /// also send data. 226 | fn send_to(&self, _addr: SocketAddr, _data: &[u8]) -> SendStatus { 227 | panic!("Adapter not configured to send messages directly from the local resource") 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/adapters/tcp.rs: -------------------------------------------------------------------------------- 1 | pub use socket2::{TcpKeepalive}; 2 | 3 | use crate::network::adapter::{ 4 | Resource, Remote, Local, Adapter, SendStatus, AcceptedType, ReadStatus, ConnectionInfo, 5 | ListeningInfo, PendingStatus, 6 | }; 7 | use crate::network::{RemoteAddr, Readiness, TransportConnect, TransportListen}; 8 | 9 | use mio::net::{TcpListener, TcpStream}; 10 | use mio::event::{Source}; 11 | 12 | use socket2::{Socket, Domain, Type, Protocol}; 13 | 14 | use std::net::{SocketAddr}; 15 | #[cfg(unix)] 16 | use std::ffi::{CString}; 17 | use std::io::{self, ErrorKind, Read, Write}; 18 | #[cfg(any(target_os = "macos", target_os = "ios"))] 19 | use std::num::NonZeroU32; 20 | use std::mem::{forget, MaybeUninit}; 21 | use std::os::raw::c_int; 22 | #[cfg(target_os = "windows")] 23 | use std::os::windows::io::{FromRawSocket, AsRawSocket}; 24 | #[cfg(not(target_os = "windows"))] 25 | use std::os::{fd::AsRawFd, unix::io::FromRawFd}; 26 | 27 | /// Size of the internal reading buffer. 28 | /// It implies that at most the generated [`crate::network::NetEvent::Message`] 29 | /// will contains a chunk of data of this value. 30 | pub const INPUT_BUFFER_SIZE: usize = u16::MAX as usize; // 2^16 - 1 31 | 32 | /// The maximum length of the pending (unaccepted) connection queue of a listener. 33 | pub const LISTENER_BACKLOG: c_int = 1024; 34 | 35 | #[derive(Clone, Debug, Default)] 36 | pub struct TcpConnectConfig { 37 | bind_device: Option, 38 | source_address: Option, 39 | keepalive: Option, 40 | } 41 | 42 | impl TcpConnectConfig { 43 | /// Bind the TCP connection to a specific interface, identified by its name. This option works 44 | /// in Unix, on other systems, it will be ignored. 45 | pub fn with_bind_device(mut self, device: String) -> Self { 46 | self.bind_device = Some(device); 47 | self 48 | } 49 | 50 | /// Enables TCP keepalive settings on the socket. 51 | pub fn with_keepalive(mut self, keepalive: TcpKeepalive) -> Self { 52 | self.keepalive = Some(keepalive); 53 | self 54 | } 55 | 56 | /// Specify the source address and port. 57 | pub fn with_source_address(mut self, source_address: SocketAddr) -> Self { 58 | self.source_address = Some(source_address); 59 | self 60 | } 61 | } 62 | 63 | #[derive(Clone, Debug, Default)] 64 | pub struct TcpListenConfig { 65 | bind_device: Option, 66 | keepalive: Option, 67 | } 68 | 69 | impl TcpListenConfig { 70 | /// Bind the TCP listener to a specific interface, identified by its name. This option works in 71 | /// Unix, on other systems, it will be ignored. 72 | pub fn with_bind_device(mut self, device: String) -> Self { 73 | self.bind_device = Some(device); 74 | self 75 | } 76 | 77 | /// Enables TCP keepalive settings on client connection sockets. 78 | pub fn with_keepalive(mut self, keepalive: TcpKeepalive) -> Self { 79 | self.keepalive = Some(keepalive); 80 | self 81 | } 82 | } 83 | 84 | pub(crate) struct TcpAdapter; 85 | impl Adapter for TcpAdapter { 86 | type Remote = RemoteResource; 87 | type Local = LocalResource; 88 | } 89 | 90 | pub(crate) struct RemoteResource { 91 | stream: TcpStream, 92 | keepalive: Option, 93 | } 94 | 95 | impl Resource for RemoteResource { 96 | fn source(&mut self) -> &mut dyn Source { 97 | &mut self.stream 98 | } 99 | } 100 | 101 | impl Remote for RemoteResource { 102 | fn connect_with( 103 | config: TransportConnect, 104 | remote_addr: RemoteAddr, 105 | ) -> io::Result> { 106 | let config = match config { 107 | TransportConnect::Tcp(config) => config, 108 | _ => panic!("Internal error: Got wrong config"), 109 | }; 110 | let peer_addr = *remote_addr.socket_addr(); 111 | 112 | let socket = Socket::new( 113 | match peer_addr { 114 | SocketAddr::V4 { .. } => Domain::IPV4, 115 | SocketAddr::V6 { .. } => Domain::IPV6, 116 | }, 117 | Type::STREAM, 118 | Some(Protocol::TCP), 119 | )?; 120 | socket.set_nonblocking(true)?; 121 | 122 | if let Some(source_address) = config.source_address { 123 | socket.bind(&source_address.into())?; 124 | } 125 | 126 | #[cfg(unix)] 127 | if let Some(bind_device) = config.bind_device { 128 | let device = CString::new(bind_device)?; 129 | 130 | #[cfg(all(not(target_os = "macos"), not(target_os = "ios")))] 131 | socket.bind_device(Some(device.as_bytes()))?; 132 | 133 | #[cfg(any(target_os = "macos", target_os = "ios"))] 134 | match NonZeroU32::new(unsafe { libc::if_nametoindex(device.as_ptr()) }) { 135 | Some(index) => socket.bind_device_by_index_v4(Some(index))?, 136 | None => { 137 | return Err(io::Error::new( 138 | ErrorKind::NotFound, 139 | "Bind device interface not found", 140 | )) 141 | } 142 | } 143 | } 144 | 145 | match socket.connect(&peer_addr.into()) { 146 | #[cfg(unix)] 147 | Err(e) if e.raw_os_error() != Some(libc::EINPROGRESS) => return Err(e), 148 | #[cfg(windows)] 149 | Err(e) if e.kind() != io::ErrorKind::WouldBlock => return Err(e), 150 | _ => {} 151 | } 152 | 153 | let stream = TcpStream::from_std(socket.into()); 154 | let local_addr = stream.local_addr()?; 155 | Ok(ConnectionInfo { 156 | remote: Self { stream, keepalive: config.keepalive }, 157 | local_addr, 158 | peer_addr, 159 | }) 160 | } 161 | 162 | fn receive(&self, mut process_data: impl FnMut(&[u8])) -> ReadStatus { 163 | let buffer: MaybeUninit<[u8; INPUT_BUFFER_SIZE]> = MaybeUninit::uninit(); 164 | let mut input_buffer = unsafe { buffer.assume_init() }; // Avoid to initialize the array 165 | 166 | loop { 167 | let mut stream = &self.stream; 168 | match stream.read(&mut input_buffer) { 169 | Ok(0) => break ReadStatus::Disconnected, 170 | Ok(size) => process_data(&input_buffer[..size]), 171 | Err(ref err) if err.kind() == ErrorKind::Interrupted => continue, 172 | Err(ref err) if err.kind() == ErrorKind::WouldBlock => { 173 | break ReadStatus::WaitNextEvent 174 | } 175 | Err(ref err) if err.kind() == ErrorKind::ConnectionReset => { 176 | break ReadStatus::Disconnected 177 | } 178 | Err(err) => { 179 | log::error!("TCP receive error: {}", err); 180 | break ReadStatus::Disconnected; // should not happen 181 | } 182 | } 183 | } 184 | } 185 | 186 | fn send(&self, data: &[u8]) -> SendStatus { 187 | // TODO: The current implementation implies an active waiting, 188 | // improve it using POLLIN instead to avoid active waiting. 189 | // Note: Despite the fear that an active waiting could generate, 190 | // this only occurs in the case when the receiver is full because reads slower that it sends. 191 | let mut total_bytes_sent = 0; 192 | loop { 193 | let mut stream = &self.stream; 194 | match stream.write(&data[total_bytes_sent..]) { 195 | Ok(bytes_sent) => { 196 | total_bytes_sent += bytes_sent; 197 | if total_bytes_sent == data.len() { 198 | break SendStatus::Sent; 199 | } 200 | } 201 | Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => continue, 202 | 203 | // Others errors are considered fatal for the connection. 204 | // a Event::Disconnection will be generated later. 205 | Err(err) => { 206 | log::error!("TCP receive error: {}", err); 207 | break SendStatus::ResourceNotFound; // should not happen 208 | } 209 | } 210 | } 211 | } 212 | 213 | fn pending(&self, _readiness: Readiness) -> PendingStatus { 214 | let status = check_stream_ready(&self.stream); 215 | 216 | if status == PendingStatus::Ready { 217 | if let Some(keepalive) = &self.keepalive { 218 | #[cfg(target_os = "windows")] 219 | let socket = unsafe { Socket::from_raw_socket(self.stream.as_raw_socket()) }; 220 | #[cfg(not(target_os = "windows"))] 221 | let socket = unsafe { Socket::from_raw_fd(self.stream.as_raw_fd()) }; 222 | 223 | if let Err(e) = socket.set_tcp_keepalive(keepalive) { 224 | log::warn!("TCP set keepalive error: {}", e); 225 | } 226 | 227 | // Don't drop so the underlying socket is not closed. 228 | forget(socket); 229 | } 230 | } 231 | 232 | status 233 | } 234 | } 235 | 236 | /// Check if a TcpStream can be considered connected. 237 | pub fn check_stream_ready(stream: &TcpStream) -> PendingStatus { 238 | // A multiplatform non-blocking way to determine if the TCP stream is connected: 239 | // Extracted from: https://github.com/tokio-rs/mio/issues/1486 240 | if let Ok(Some(_)) = stream.take_error() { 241 | return PendingStatus::Disconnected; 242 | } 243 | match stream.peer_addr() { 244 | Ok(_) => PendingStatus::Ready, 245 | Err(err) if err.kind() == io::ErrorKind::NotConnected => PendingStatus::Incomplete, 246 | Err(err) if err.kind() == io::ErrorKind::InvalidInput => PendingStatus::Incomplete, 247 | Err(_) => PendingStatus::Disconnected, 248 | } 249 | } 250 | 251 | pub(crate) struct LocalResource { 252 | listener: TcpListener, 253 | keepalive: Option, 254 | } 255 | 256 | impl Resource for LocalResource { 257 | fn source(&mut self) -> &mut dyn Source { 258 | &mut self.listener 259 | } 260 | } 261 | 262 | impl Local for LocalResource { 263 | type Remote = RemoteResource; 264 | 265 | fn listen_with(config: TransportListen, addr: SocketAddr) -> io::Result> { 266 | let config = match config { 267 | TransportListen::Tcp(config) => config, 268 | _ => panic!("Internal error: Got wrong config"), 269 | }; 270 | 271 | let socket = Socket::new( 272 | match addr { 273 | SocketAddr::V4 { .. } => Domain::IPV4, 274 | SocketAddr::V6 { .. } => Domain::IPV6, 275 | }, 276 | Type::STREAM, 277 | Some(Protocol::TCP), 278 | )?; 279 | socket.set_nonblocking(true)?; 280 | socket.set_reuse_address(true)?; 281 | 282 | #[cfg(unix)] 283 | if let Some(bind_device) = config.bind_device { 284 | let device = CString::new(bind_device)?; 285 | 286 | #[cfg(all(not(target_os = "macos"), not(target_os = "ios")))] 287 | socket.bind_device(Some(device.as_bytes()))?; 288 | 289 | #[cfg(any(target_os = "macos", target_os = "ios"))] 290 | match NonZeroU32::new(unsafe { libc::if_nametoindex(device.as_ptr()) }) { 291 | Some(index) => socket.bind_device_by_index_v4(Some(index))?, 292 | None => { 293 | return Err(io::Error::new( 294 | ErrorKind::NotFound, 295 | "Bind device interface not found", 296 | )) 297 | } 298 | } 299 | } 300 | 301 | socket.bind(&addr.into())?; 302 | socket.listen(LISTENER_BACKLOG)?; 303 | 304 | let listener = TcpListener::from_std(socket.into()); 305 | 306 | let local_addr = listener.local_addr().unwrap(); 307 | Ok(ListeningInfo { 308 | local: { LocalResource { listener, keepalive: config.keepalive } }, 309 | local_addr, 310 | }) 311 | } 312 | 313 | fn accept(&self, mut accept_remote: impl FnMut(AcceptedType<'_, Self::Remote>)) { 314 | loop { 315 | match self.listener.accept() { 316 | Ok((stream, addr)) => accept_remote(AcceptedType::Remote( 317 | addr, 318 | RemoteResource { stream, keepalive: self.keepalive.clone() }, 319 | )), 320 | Err(ref err) if err.kind() == ErrorKind::WouldBlock => break, 321 | Err(ref err) if err.kind() == ErrorKind::Interrupted => continue, 322 | Err(err) => break log::error!("TCP accept error: {}", err), // Should not happen 323 | } 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/crates/v/message-io)](https://crates.io/crates/message-io) 2 | [![](https://img.shields.io/docsrs/message-io)](https://docs.rs/message-io) 3 | [![](https://img.shields.io/crates/l/message-io)](https://www.apache.org/licenses/LICENSE-2.0.txt) 4 | [![](https://img.shields.io/crates/d/message-io)](https://crates.io/crates/message-io) 5 | [![](https://img.shields.io/github/actions/workflow/status/lemunozm/message-io/.github/workflows/rust.yml?branch=master)](https://github.com/lemunozm/message-io/actions?query=workflow%3A%22message-io+ci%22) 6 | [![](https://img.shields.io/badge/buymeacoffee-donate-yellow)](https://www.buymeacoffee.com/lemunozm) 7 | 8 |

9 | 10 |

11 | 12 | `message-io` is a fast and easy-to-use event-driven network library. 13 | The library handles the OS socket internally and offers a simple event message API to the user. 14 | It also allows you to make an adapter for your own transport protocol following some 15 | [rules](#custom-adapter), delegating the tedious asynchrony and thread management to the library. 16 | 17 |

18 | 19 |

20 | 21 | If you find a problem using the library or you have an idea to improve it, 22 | do not hesitate to open an issue. **Any contribution is welcome!** 23 | And remember: more [caffeine](https://www.buymeacoffee.com/lemunozm), more productive! 24 | 25 | ## Motivation 26 | Managing sockets is hard because you need to fight with threads, concurrency, full duplex, encoding, 27 | IO errors that come from the OS (which are really difficult to understand in some situations), etc. 28 | If you make use of *non-blocking* sockets, it adds a new layer of complexity: 29 | synchronize the events that come asynchronously from the Operating System. 30 | 31 | `message-io` offers an easy way to deal with all these aforementioned problems, 32 | making them transparent for you, 33 | the programmer that wants to make an application with its own problems. 34 | For that, the library gives you a simple API with two concepts to understand: 35 | **messages** (the data you send and receive), and **endpoints** (the recipients of that data). 36 | This abstraction also offers the possibility to use the same API independently 37 | of the transport protocol used. 38 | You could change the transport of your application in literally one line. 39 | 40 | ## Features 41 | - Highly scalable: **non-blocking sockets** that allow for the management of thousands of active connections. 42 | - Multiplatform: see [mio platform support](https://github.com/tokio-rs/mio#platforms). 43 | - Multiple transport protocols 44 | ([docs](https://docs.rs/message-io/latest/message_io/network/enum.Transport.html)): 45 | - **TCP**: stream and framed mode (to deal with messages instead of stream) 46 | - **UDP**, with multicast option 47 | - **WebSocket**: plain and ~~secure~~[#102](https://github.com/lemunozm/message-io/issues/102) 48 | option using [tungstenite-rs](https://github.com/snapview/tungstenite-rs) 49 | (`wasm` is not supported but [planned](https://github.com/lemunozm/message-io/issues/100)). 50 | - Custom FIFO events with timers and priority. 51 | - Easy, intuitive and consistent API: 52 | - Follows [KISS principle](https://en.wikipedia.org/wiki/KISS_principle). 53 | - Abstraction from transport layer: don't think about sockets, think about messages and endpoints. 54 | - Only two main entities to use: 55 | - a [`NodeHandler`](https://docs.rs/message-io/latest/message_io/node/struct.NodeHandler.html) 56 | to manage all connections (connect, listen, remove, send) and signals (timers, priority). 57 | - a [`NodeListener`](https://docs.rs/message-io/latest/message_io/node/struct.NodeListener.html) 58 | to process all signals and events from the network. 59 | - Forget concurrency problems: handle all connection and listeners from one thread: 60 | "One thread to rule them all". 61 | - Easy error handling: 62 | do not deal with dark internal `std::io::Error` when sending/receiving from the network. 63 | - High performance (see the [benchmarks](docs/performance_benchmarks.md)): 64 | - Write/read messages with zero-copy. 65 | You write and read directly from the internal OS socket buffer without any copy in the middle by the library. 66 | - Full duplex: simultaneous reading/writing operations over the same internal OS socket. 67 | - Customizable: `message-io` doesn't have the transport you need? 68 | Easily add an [adapter](#custom-adapter). 69 | 70 | ## Documentation 71 | - [API documentation](https://docs.rs/message-io/) 72 | - [Basic concepts](docs/basic_concepts.md) 73 | - [Benchmarks](docs/performance_benchmarks.md) 74 | - [Examples](examples): 75 | - [Ping Pong](examples/ping-pong) (a simple client/server example) 76 | - [Multicast](examples/multicast) 77 | - [Distributed network with discovery server](examples/distributed) 78 | - [File transfer](examples/file-transfer) 79 | - [Open Source applications](#app-list) 80 | 81 | ## Getting started 82 | Add to your `Cargo.toml` (all transports included by default): 83 | ```toml 84 | [dependencies] 85 | message-io = "0.19" 86 | ``` 87 | If you **only** want to use a subset of the available transport battery, 88 | you can select them by their associated features `tcp`, `udp`, and `websocket`. 89 | For example, in order to include only *TCP* and *UDP*, add to your `Cargo.toml`: 90 | ```toml 91 | [dependencies] 92 | message-io = { version = "0.19", default-features = false, features = ["tcp", "udp"] } 93 | ``` 94 | 95 | ### All in one: TCP, UDP and WebSocket echo server 96 | The following example is the simplest server that reads messages from the clients and responds 97 | to them with the same message. 98 | It is able to offer the "service" for 3 differents protocols at the same time. 99 | 100 | ```rust,no_run 101 | use message_io::node::{self}; 102 | use message_io::network::{NetEvent, Transport}; 103 | 104 | fn main() { 105 | // Create a node, the main message-io entity. It is divided in 2 parts: 106 | // The 'handler', used to make actions (connect, send messages, signals, stop the node...) 107 | // The 'listener', used to read events from the network or signals. 108 | let (handler, listener) = node::split::<()>(); 109 | 110 | // Listen for TCP, UDP and WebSocket messages at the same time. 111 | handler.network().listen(Transport::FramedTcp, "0.0.0.0:3042").unwrap(); 112 | handler.network().listen(Transport::Udp, "0.0.0.0:3043").unwrap(); 113 | handler.network().listen(Transport::Ws, "0.0.0.0:3044").unwrap(); 114 | 115 | // Read incoming network events. 116 | listener.for_each(move |event| match event.network() { 117 | NetEvent::Connected(_, _) => unreachable!(), // Used for explicit connections. 118 | NetEvent::Accepted(_endpoint, _listener) => println!("Client connected"), // Tcp or Ws 119 | NetEvent::Message(endpoint, data) => { 120 | println!("Received: {}", String::from_utf8_lossy(data)); 121 | handler.network().send(endpoint, data); 122 | }, 123 | NetEvent::Disconnected(_endpoint) => println!("Client disconnected"), //Tcp or Ws 124 | }); 125 | } 126 | ``` 127 | 128 | ### Echo client 129 | The following example shows a client that can connect to the previous server. 130 | It sends a message each second to the server and listen its echo response. 131 | Changing the `Transport::FramedTcp` to `Udp` or `Ws` will change the underlying transport used. 132 | 133 | ```rust,no_run 134 | use message_io::node::{self, NodeEvent}; 135 | use message_io::network::{NetEvent, Transport}; 136 | use std::time::Duration; 137 | 138 | enum Signal { 139 | Greet, 140 | // Any other app event here. 141 | } 142 | 143 | fn main() { 144 | let (handler, listener) = node::split(); 145 | 146 | // You can change the transport to Udp or Ws (WebSocket). 147 | let (server, _) = handler.network().connect(Transport::FramedTcp, "127.0.0.1:3042").unwrap(); 148 | 149 | listener.for_each(move |event| match event { 150 | NodeEvent::Network(net_event) => match net_event { 151 | NetEvent::Connected(_endpoint, _ok) => handler.signals().send(Signal::Greet), 152 | NetEvent::Accepted(_, _) => unreachable!(), // Only generated by listening 153 | NetEvent::Message(_endpoint, data) => { 154 | println!("Received: {}", String::from_utf8_lossy(data)); 155 | }, 156 | NetEvent::Disconnected(_endpoint) => (), 157 | } 158 | NodeEvent::Signal(signal) => match signal { 159 | Signal::Greet => { // computed every second 160 | handler.network().send(server, "Hello server!".as_bytes()); 161 | handler.signals().send_with_timer(Signal::Greet, Duration::from_secs(1)); 162 | } 163 | } 164 | }); 165 | } 166 | ``` 167 | 168 | ### Test it yourself! 169 | Clone the repository and test the *Ping Pong* example 170 | (similar to the *README* example but more vitaminized). 171 | 172 | Run the server: 173 | ```sh 174 | cargo run --example ping-pong server tcp 3456 175 | ``` 176 | Run the client: 177 | ```sh 178 | cargo run --example ping-pong client tcp 127.0.0.1:3456 179 | ``` 180 | 181 | You can play with it by changing the transport, running several clients, disconnecting them, etc. 182 | See more [here](examples/ping-pong). 183 | 184 | ## Do you need a transport protocol that `message-io` doesn't have? Add an adapter! 185 | 186 | `message-io` offers two *kinds* of API. 187 | The **user API** that talks to `message-io` itself as a user of the library, 188 | and the internal **adapter API** for those who want to add their protocol adapters into the library. 189 | 190 |

191 | 192 |

193 | 194 | If a transport protocol can be built in top of [`mio`](https://github.com/tokio-rs/mio) 195 | (most of the existing protocol libraries can), then you can add it to `message-io` **really easily**: 196 | 197 | 1. Add your *adapter* file in `src/adapters/.rs` that implements the 198 | traits that you find [here](https://docs.rs/message-io/latest/message_io/network/adapter/index.html). 199 | It contains only 8 mandatory functions to implement (see the [template](src/adapters/template.rs)), 200 | and it takes arround 150 lines to implement an adapter. 201 | 202 | 1. Add a new field in the `Transport` enum found in 203 | [src/network/transport.rs](src/network/transport.rs) to register your new adapter. 204 | 205 | That's all. 206 | You can use your new transport with the `message-io` API like any other. 207 | 208 | Oops! one more step: make a *Pull Request* so everyone can use it :) 209 | 210 | ## Open source projects using `message-io` 211 | - [Termchat](https://github.com/lemunozm/termchat) Terminal chat through the LAN with video streaming and file transfer. 212 | - [Egregoria](https://github.com/Uriopass/Egregoria) Contemplative society simulation. 213 | - [Project-Midas](https://github.com/ray33ee/Project-Midas) Distributed network based parallel computing system. 214 | - [AsciiArena](https://github.com/lemunozm/asciiarena) Terminal multiplayer death match game (alpha). 215 | - [LanChat](https://github.com/sigmaSd/LanChat) LanChat flutter + rust demo. 216 | 217 | *Does your awesome project use `message-io`? Make a Pull Request and add it to the list!* 218 | 219 | ## Is message-io for me? 220 | `message-io` has the main goal to keep things simple. 221 | This is great, but sometimes this point of view could make more complex the already complex things. 222 | 223 | For instance, `message-io` allows handling asynchronous network events without using an `async/await` pattern. 224 | It reduces the complexity to handle income messages from the network, which is great. 225 | Nevertheless, the applications that read asynchronous messages tend to perform 226 | asynchronous tasks over these events too. 227 | This asynchronous inheritance can easily be propagated to your entire application 228 | being difficult to maintain or scale without an async/await pattern. 229 | In those cases, maybe [`tokio`](https://tokio.rs) could be a better option. 230 | You need to deal with more low-level network stuff but you gain in organization and thread/resource management. 231 | 232 | A similar issue can happen regarding the node usage of `message-io`. 233 | Because a node can be used independently as a client/server or both, 234 | you can easily start to make peer to peer applications. 235 | In fact, this is one of the intentions of `message-io`. 236 | Nevertheless, if your goal scales, will appear problems related to this patter to deal with, 237 | and libraries such as [`libp2p`](https://libp2p.io) come with a huge battery of tools to help to archive that goal. 238 | 239 | Of course, this is not a disclaiming about the library usage (I use it!), 240 | it is more about being honest about its capabilities, 241 | and to guide you to the right tool depending on what are you looking for. 242 | 243 | To summarize: 244 | 245 | - If you have a medium complex network problem: make it simpler with `message-io`! 246 | - If you have a really complex network problem: use 247 | [`tokio`](https://tokio.rs), [`libp2p`](https://libp2p.io) or others, to have more control over it. 248 | -------------------------------------------------------------------------------- /src/network/driver.rs: -------------------------------------------------------------------------------- 1 | use super::endpoint::{Endpoint}; 2 | use super::resource_id::{ResourceId, ResourceType}; 3 | use super::poll::{Poll, Readiness}; 4 | use super::registry::{ResourceRegistry, Register}; 5 | use super::remote_addr::{RemoteAddr}; 6 | use super::adapter::{Adapter, Remote, Local, SendStatus, AcceptedType, ReadStatus, PendingStatus}; 7 | use super::transport::{TransportConnect, TransportListen}; 8 | 9 | use std::net::{SocketAddr}; 10 | use std::sync::{ 11 | Arc, 12 | atomic::{AtomicBool, Ordering}, 13 | }; 14 | use std::io::{self}; 15 | 16 | #[cfg(doctest)] 17 | use super::transport::{Transport}; 18 | 19 | /// Enum used to describe a network event that an internal transport adapter has produced. 20 | pub enum NetEvent<'a> { 21 | /// Connection result. 22 | /// This event is only generated after a [`crate::network::NetworkController::connect()`] 23 | /// call. 24 | /// The event contains the endpoint of the connection 25 | /// (same endpoint returned by the `connect()` method), 26 | /// and a boolean indicating the *result* of that connection. 27 | /// In *non connection-oriented transports* as *UDP* it simply means that the resource 28 | /// is ready to use, and the boolean will be always `true`. 29 | /// In connection-oriented transports it means that the handshake has been performed, and the 30 | /// connection is established and ready to use. 31 | /// Since this handshake could fail, the boolean could be `false`. 32 | Connected(Endpoint, bool), 33 | 34 | /// New endpoint has been accepted by a listener and considered ready to use. 35 | /// The event contains the resource id of the listener that accepted this connection. 36 | /// 37 | /// Note that this event will only be generated by connection-oriented transports as *TCP*. 38 | Accepted(Endpoint, ResourceId), 39 | 40 | /// Input message received by the network. 41 | /// In packet-based transports, the data of a message sent corresponds with the data of this 42 | /// event. This one-to-one relation is not conserved in stream-based transports as *TCP*. 43 | /// 44 | /// If you want a packet-based protocol over *TCP* use 45 | /// [`crate::network::Transport::FramedTcp`]. 46 | Message(Endpoint, &'a [u8]), 47 | 48 | /// This event is only dispatched when a connection is lost. 49 | /// Remove explicitely a resource will NOT generate the event. 50 | /// When this event is received, the resource is considered already removed, 51 | /// the user do not need to remove it after this event. 52 | /// A [`NetEvent::Message`] event will never be generated after this event from this endpoint. 53 | /// 54 | /// Note that this event will only be generated by connection-oriented transports as *TCP*. 55 | /// *UDP*, for example, is NOT connection-oriented, and the event can no be detected. 56 | Disconnected(Endpoint), 57 | } 58 | 59 | impl std::fmt::Debug for NetEvent<'_> { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | let string = match self { 62 | Self::Connected(endpoint, status) => format!("Connected({endpoint}, {status})"), 63 | Self::Accepted(endpoint, id) => format!("Accepted({endpoint}, {id})"), 64 | Self::Message(endpoint, data) => format!("Message({}, {})", endpoint, data.len()), 65 | Self::Disconnected(endpoint) => format!("Disconnected({endpoint})"), 66 | }; 67 | write!(f, "NetEvent::{string}") 68 | } 69 | } 70 | 71 | pub trait ActionController: Send + Sync { 72 | fn connect_with( 73 | &self, 74 | config: TransportConnect, 75 | addr: RemoteAddr, 76 | ) -> io::Result<(Endpoint, SocketAddr)>; 77 | fn listen_with( 78 | &self, 79 | config: TransportListen, 80 | addr: SocketAddr, 81 | ) -> io::Result<(ResourceId, SocketAddr)>; 82 | fn send(&self, endpoint: Endpoint, data: &[u8]) -> SendStatus; 83 | fn remove(&self, id: ResourceId) -> bool; 84 | fn is_ready(&self, id: ResourceId) -> Option; 85 | } 86 | 87 | pub trait EventProcessor: Send + Sync { 88 | fn process(&self, id: ResourceId, readiness: Readiness, callback: &mut dyn FnMut(NetEvent<'_>)); 89 | } 90 | 91 | struct RemoteProperties { 92 | peer_addr: SocketAddr, 93 | local: Option, 94 | ready: AtomicBool, 95 | } 96 | 97 | impl RemoteProperties { 98 | fn new(peer_addr: SocketAddr, local: Option) -> Self { 99 | Self { peer_addr, local, ready: AtomicBool::new(false) } 100 | } 101 | 102 | pub fn is_ready(&self) -> bool { 103 | self.ready.load(Ordering::Relaxed) 104 | } 105 | 106 | pub fn mark_as_ready(&self) { 107 | self.ready.store(true, Ordering::Relaxed); 108 | } 109 | } 110 | 111 | struct LocalProperties; 112 | 113 | pub struct Driver { 114 | remote_registry: Arc>, 115 | local_registry: Arc>, 116 | } 117 | 118 | impl Driver { 119 | pub fn new( 120 | _: impl Adapter, 121 | adapter_id: u8, 122 | poll: &mut Poll, 123 | ) -> Driver { 124 | let remote_poll_registry = poll.create_registry(adapter_id, ResourceType::Remote); 125 | let local_poll_registry = poll.create_registry(adapter_id, ResourceType::Local); 126 | 127 | Driver { 128 | remote_registry: Arc::new(ResourceRegistry::::new( 129 | remote_poll_registry, 130 | )), 131 | local_registry: Arc::new(ResourceRegistry::::new( 132 | local_poll_registry, 133 | )), 134 | } 135 | } 136 | } 137 | 138 | impl Clone for Driver { 139 | fn clone(&self) -> Driver { 140 | Driver { 141 | remote_registry: self.remote_registry.clone(), 142 | local_registry: self.local_registry.clone(), 143 | } 144 | } 145 | } 146 | 147 | impl ActionController for Driver { 148 | fn connect_with( 149 | &self, 150 | config: TransportConnect, 151 | addr: RemoteAddr, 152 | ) -> io::Result<(Endpoint, SocketAddr)> { 153 | R::connect_with(config, addr).map(|info| { 154 | let id = self.remote_registry.register( 155 | info.remote, 156 | RemoteProperties::new(info.peer_addr, None), 157 | true, 158 | ); 159 | (Endpoint::new(id, info.peer_addr), info.local_addr) 160 | }) 161 | } 162 | 163 | fn listen_with( 164 | &self, 165 | config: TransportListen, 166 | addr: SocketAddr, 167 | ) -> io::Result<(ResourceId, SocketAddr)> { 168 | L::listen_with(config, addr).map(|info| { 169 | let id = self.local_registry.register(info.local, LocalProperties, false); 170 | (id, info.local_addr) 171 | }) 172 | } 173 | 174 | fn send(&self, endpoint: Endpoint, data: &[u8]) -> SendStatus { 175 | match endpoint.resource_id().resource_type() { 176 | ResourceType::Remote => match self.remote_registry.get(endpoint.resource_id()) { 177 | Some(remote) => match remote.properties.is_ready() { 178 | true => remote.resource.send(data), 179 | false => SendStatus::ResourceNotAvailable, 180 | }, 181 | None => SendStatus::ResourceNotFound, 182 | }, 183 | ResourceType::Local => match self.local_registry.get(endpoint.resource_id()) { 184 | Some(remote) => remote.resource.send_to(endpoint.addr(), data), 185 | None => SendStatus::ResourceNotFound, 186 | }, 187 | } 188 | } 189 | 190 | fn remove(&self, id: ResourceId) -> bool { 191 | match id.resource_type() { 192 | ResourceType::Remote => self.remote_registry.deregister(id), 193 | ResourceType::Local => self.local_registry.deregister(id), 194 | } 195 | } 196 | 197 | fn is_ready(&self, id: ResourceId) -> Option { 198 | match id.resource_type() { 199 | ResourceType::Remote => self.remote_registry.get(id).map(|r| r.properties.is_ready()), 200 | ResourceType::Local => self.local_registry.get(id).map(|_| true), 201 | } 202 | } 203 | } 204 | 205 | impl> EventProcessor for Driver { 206 | fn process( 207 | &self, 208 | id: ResourceId, 209 | readiness: Readiness, 210 | event_callback: &mut dyn FnMut(NetEvent<'_>), 211 | ) { 212 | match id.resource_type() { 213 | ResourceType::Remote => { 214 | if let Some(remote) = self.remote_registry.get(id) { 215 | let endpoint = Endpoint::new(id, remote.properties.peer_addr); 216 | log::trace!("Processed remote for {}", endpoint); 217 | 218 | if !remote.properties.is_ready() { 219 | self.resolve_pending_remote(&remote, endpoint, readiness, |e| { 220 | event_callback(e) 221 | }); 222 | } 223 | if remote.properties.is_ready() { 224 | match readiness { 225 | Readiness::Write => { 226 | self.write_to_remote(&remote, endpoint, event_callback); 227 | } 228 | Readiness::Read => { 229 | self.read_from_remote(&remote, endpoint, event_callback); 230 | } 231 | } 232 | } 233 | } 234 | } 235 | ResourceType::Local => { 236 | if let Some(local) = self.local_registry.get(id) { 237 | log::trace!("Processed local for {}", id); 238 | match readiness { 239 | Readiness::Write => (), 240 | Readiness::Read => self.read_from_local(&local, id, event_callback), 241 | } 242 | } 243 | } 244 | } 245 | } 246 | } 247 | 248 | impl> Driver { 249 | fn resolve_pending_remote( 250 | &self, 251 | remote: &Arc>, 252 | endpoint: Endpoint, 253 | readiness: Readiness, 254 | mut event_callback: impl FnMut(NetEvent<'_>), 255 | ) { 256 | let status = remote.resource.pending(readiness); 257 | log::trace!("Resolve pending for {}: {:?}", endpoint, status); 258 | match status { 259 | PendingStatus::Ready => { 260 | remote.properties.mark_as_ready(); 261 | match remote.properties.local { 262 | Some(listener_id) => event_callback(NetEvent::Accepted(endpoint, listener_id)), 263 | None => event_callback(NetEvent::Connected(endpoint, true)), 264 | } 265 | remote.resource.ready_to_write(); 266 | } 267 | PendingStatus::Incomplete => (), 268 | PendingStatus::Disconnected => { 269 | self.remote_registry.deregister(endpoint.resource_id()); 270 | if remote.properties.local.is_none() { 271 | event_callback(NetEvent::Connected(endpoint, false)); 272 | } 273 | } 274 | } 275 | } 276 | 277 | fn write_to_remote( 278 | &self, 279 | remote: &Arc>, 280 | endpoint: Endpoint, 281 | mut event_callback: impl FnMut(NetEvent<'_>), 282 | ) { 283 | if !remote.resource.ready_to_write() { 284 | event_callback(NetEvent::Disconnected(endpoint)); 285 | } 286 | } 287 | 288 | fn read_from_remote( 289 | &self, 290 | remote: &Arc>, 291 | endpoint: Endpoint, 292 | mut event_callback: impl FnMut(NetEvent<'_>), 293 | ) { 294 | let status = 295 | remote.resource.receive(|data| event_callback(NetEvent::Message(endpoint, data))); 296 | log::trace!("Receive status: {:?}", status); 297 | if let ReadStatus::Disconnected = status { 298 | // Checked because, the user in the callback could have removed the same resource. 299 | if self.remote_registry.deregister(endpoint.resource_id()) { 300 | event_callback(NetEvent::Disconnected(endpoint)); 301 | } 302 | } 303 | } 304 | 305 | fn read_from_local( 306 | &self, 307 | local: &Arc>, 308 | id: ResourceId, 309 | mut event_callback: impl FnMut(NetEvent<'_>), 310 | ) { 311 | local.resource.accept(|accepted| { 312 | log::trace!("Accepted type: {}", accepted); 313 | match accepted { 314 | AcceptedType::Remote(addr, remote) => { 315 | self.remote_registry.register( 316 | remote, 317 | RemoteProperties::new(addr, Some(id)), 318 | true, 319 | ); 320 | } 321 | AcceptedType::Data(addr, data) => { 322 | let endpoint = Endpoint::new(id, addr); 323 | event_callback(NetEvent::Message(endpoint, data)); 324 | } 325 | } 326 | }); 327 | } 328 | } 329 | 330 | impl std::fmt::Display for AcceptedType<'_, R> { 331 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 332 | let string = match self { 333 | AcceptedType::Remote(addr, _) => format!("Remote({addr})"), 334 | AcceptedType::Data(addr, _) => format!("Data({addr})"), 335 | }; 336 | write!(f, "AcceptedType::{string}") 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/util/encoding.rs: -------------------------------------------------------------------------------- 1 | use integer_encoding::VarInt; 2 | 3 | /// This is the max required bytes to encode a u64 using the varint encoding scheme. 4 | /// It is size 10=ceil(64/7) 5 | pub const MAX_ENCODED_SIZE: usize = 10; 6 | 7 | /// Encode a message, returning the bytes that must be sent before the message. 8 | /// A buffer is used to avoid heap allocation. 9 | pub fn encode_size<'a>(message: &[u8], buf: &'a mut [u8; MAX_ENCODED_SIZE]) -> &'a [u8] { 10 | let varint_size = message.len().encode_var(buf); 11 | &buf[..varint_size] 12 | } 13 | 14 | /// Decodes an encoded value in a buffer. 15 | /// The function returns the message size and the consumed bytes or none if the buffer is too small. 16 | pub fn decode_size(data: &[u8]) -> Option<(usize, usize)> { 17 | usize::decode_var(data) 18 | } 19 | 20 | /// Used to decoded messages from several/partial data chunks 21 | pub struct Decoder { 22 | stored: Vec, 23 | } 24 | 25 | impl Default for Decoder { 26 | /// Creates a new decoder. 27 | /// It will only reserve memory in cases where decoding needs to keep data among messages. 28 | fn default() -> Decoder { 29 | Decoder { stored: Vec::new() } 30 | } 31 | } 32 | 33 | impl Decoder { 34 | fn try_decode(&mut self, data: &[u8], mut decoded_callback: impl FnMut(&[u8])) { 35 | let mut next_data = data; 36 | loop { 37 | if let Some((expected_size, used_bytes)) = decode_size(next_data) { 38 | let remaining = &next_data[used_bytes..]; 39 | if remaining.len() >= expected_size { 40 | let (decoded, not_decoded) = remaining.split_at(expected_size); 41 | decoded_callback(decoded); 42 | if !not_decoded.is_empty() { 43 | next_data = not_decoded; 44 | continue; 45 | } 46 | else { 47 | break; 48 | } 49 | } 50 | } 51 | self.stored.extend_from_slice(next_data); 52 | break; 53 | } 54 | } 55 | 56 | fn store_and_decoded_data<'a>(&mut self, data: &'a [u8]) -> Option<(&[u8], &'a [u8])> { 57 | // Process frame header 58 | let ((expected_size, used_bytes), data) = match decode_size(&self.stored) { 59 | Some(size_info) => (size_info, data), 60 | None => { 61 | // we append at most the potential data needed to decode the size 62 | let max_remaining = (MAX_ENCODED_SIZE - self.stored.len()).min(data.len()); 63 | self.stored.extend_from_slice(&data[..max_remaining]); 64 | 65 | if let Some(x) = decode_size(&self.stored) { 66 | // Now we know the size 67 | (x, &data[max_remaining..]) 68 | } 69 | else { 70 | // We still don't know the size (data was too small) 71 | return None; 72 | } 73 | } 74 | }; 75 | 76 | // At this point we know at least the expected size of the frame. 77 | let remaining = expected_size - (self.stored.len() - used_bytes); 78 | if data.len() < remaining { 79 | // We need more data to decoder 80 | self.stored.extend_from_slice(data); 81 | None 82 | } 83 | else { 84 | // We can complete a message here 85 | let (to_store, remaining) = data.split_at(remaining); 86 | self.stored.extend_from_slice(to_store); 87 | Some((&self.stored[used_bytes..], remaining)) 88 | } 89 | } 90 | 91 | /// Tries to decode data without reserve any memory, direcly from `data`. 92 | /// `decoded_callback` will be called for each decoded message. 93 | /// If `data` is not enough to decoding a message, the data will be stored 94 | /// until more data is decoded (more successives calls to this function). 95 | pub fn decode(&mut self, data: &[u8], mut decoded_callback: impl FnMut(&[u8])) { 96 | if self.stored.is_empty() { 97 | self.try_decode(data, decoded_callback); 98 | } 99 | else { 100 | //There was already data in the Decoder 101 | if let Some((decoded_data, remaining)) = self.store_and_decoded_data(data) { 102 | decoded_callback(decoded_data); 103 | self.stored.clear(); 104 | self.try_decode(remaining, decoded_callback); 105 | } 106 | } 107 | } 108 | 109 | /// Returns the bytes len stored in this decoder. 110 | /// It can include both, the padding bytes and the data message bytes. 111 | /// After decoding a message, its bytes are removed from the decoder. 112 | pub fn stored_size(&self) -> usize { 113 | self.stored.len() 114 | } 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | use super::*; 120 | 121 | const MESSAGE_SIZE: usize = 20; // only works if (X + PADDING ) % 6 == 0 122 | const ENCODED_MESSAGE_SIZE: usize = 1 + MESSAGE_SIZE; // 1 = log_2(20)/7 123 | const MESSAGE: [u8; MESSAGE_SIZE] = [42; MESSAGE_SIZE]; 124 | const MESSAGE_A: [u8; MESSAGE_SIZE] = ['A' as u8; MESSAGE_SIZE]; 125 | const MESSAGE_B: [u8; MESSAGE_SIZE] = ['B' as u8; MESSAGE_SIZE]; 126 | const MESSAGE_C: [u8; MESSAGE_SIZE] = ['C' as u8; MESSAGE_SIZE]; 127 | 128 | fn encode_message(buffer: &mut Vec, message: &[u8]) { 129 | let mut buf = [0; MAX_ENCODED_SIZE]; 130 | buffer.extend_from_slice(&*encode_size(message, &mut buf)); 131 | buffer.extend_from_slice(message); 132 | } 133 | 134 | #[test] 135 | fn encode_one_message() { 136 | let mut buffer = Vec::new(); 137 | encode_message(&mut buffer, &MESSAGE); 138 | 139 | assert_eq!(ENCODED_MESSAGE_SIZE, buffer.len()); 140 | let (expected_size, used_bytes) = decode_size(&buffer).unwrap(); 141 | assert_eq!(MESSAGE_SIZE, expected_size); 142 | assert_eq!(used_bytes, 1); 143 | assert_eq!(&MESSAGE, &buffer[used_bytes..]); 144 | } 145 | 146 | #[test] 147 | fn encode_one_big_message() { 148 | let mut buffer = Vec::new(); 149 | encode_message(&mut buffer, &vec![0; 1000]); 150 | 151 | assert_eq!(1002, buffer.len()); 152 | let (expected_size, used_bytes) = decode_size(&buffer).unwrap(); 153 | assert_eq!(1000, expected_size); 154 | assert_eq!(used_bytes, 2); 155 | assert_eq!(&vec![0; 1000], &buffer[used_bytes..]); 156 | } 157 | 158 | #[test] 159 | // [ data ] 160 | // [message] 161 | fn decode_one_message() { 162 | let mut buffer = Vec::new(); 163 | encode_message(&mut buffer, &MESSAGE); 164 | 165 | let mut decoder = Decoder::default(); 166 | let mut times_called = 0; 167 | decoder.decode(&buffer, |decoded| { 168 | times_called += 1; 169 | assert_eq!(MESSAGE, decoded); 170 | }); 171 | 172 | assert_eq!(1, times_called); 173 | assert_eq!(0, decoder.stored.len()); 174 | } 175 | 176 | #[test] 177 | // [ 4B ] 178 | // [ message ] 179 | fn decode_message_no_size() { 180 | let mut buffer = Vec::new(); 181 | encode_message(&mut buffer, &[]); 182 | 183 | let mut decoder = Decoder::default(); 184 | 185 | let mut times_called = 0; 186 | decoder.decode(&buffer, |_decoded| { 187 | // Should not be called 188 | times_called += 1; 189 | }); 190 | 191 | assert_eq!(1, times_called); 192 | assert_eq!(0, decoder.stored.len()); 193 | } 194 | 195 | #[test] 196 | // [ 5B ] 197 | // [ message ] 198 | fn decode_message_one_byte() { 199 | let mut buffer = Vec::new(); 200 | encode_message(&mut buffer, &[0xFF]); 201 | 202 | let mut decoder = Decoder::default(); 203 | 204 | let mut times_called = 0; 205 | decoder.decode(&buffer, |decoded| { 206 | times_called += 1; 207 | assert_eq!([0xFF], decoded); 208 | }); 209 | 210 | assert_eq!(1, times_called); 211 | assert_eq!(0, decoder.stored.len()); 212 | } 213 | 214 | #[test] 215 | // [ data ] 216 | // [message][message][message] 217 | fn decode_multiple_messages_exact() { 218 | let mut buffer = Vec::new(); 219 | 220 | let messages = [&MESSAGE_A, &MESSAGE_B, &MESSAGE_C]; 221 | encode_message(&mut buffer, messages[0]); 222 | encode_message(&mut buffer, messages[1]); 223 | encode_message(&mut buffer, messages[2]); 224 | 225 | let mut decoder = Decoder::default(); 226 | 227 | let mut times_called = 0; 228 | decoder.decode(&buffer, |decoded| { 229 | assert_eq!(messages[times_called], decoded); 230 | times_called += 1; 231 | }); 232 | 233 | assert_eq!(3, times_called); 234 | assert_eq!(0, decoder.stored.len()); 235 | } 236 | 237 | #[test] 238 | // [ data ][ data ] 239 | // [ message ] 240 | fn decode_one_message_in_two_parts() { 241 | let mut buffer = Vec::new(); 242 | encode_message(&mut buffer, &MESSAGE); 243 | 244 | const SPLIT: usize = ENCODED_MESSAGE_SIZE / 2; 245 | let (first, second) = buffer.split_at(SPLIT); 246 | 247 | let mut decoder = Decoder::default(); 248 | 249 | let mut times_called = 0; 250 | decoder.decode(&first, |_decoded| { 251 | // Should not be called 252 | times_called += 1; 253 | }); 254 | 255 | assert_eq!(0, times_called); 256 | assert_eq!(SPLIT, decoder.stored.len()); 257 | 258 | decoder.decode(&second, |decoded| { 259 | times_called += 1; 260 | assert_eq!(MESSAGE, decoded); 261 | }); 262 | 263 | assert_eq!(1, times_called); 264 | assert_eq!(0, decoder.stored.len()); 265 | } 266 | 267 | #[test] 268 | // [ data ][ data ] 269 | // [ message ][ message ] 270 | fn decode_two_messages_in_two_parts() { 271 | let mut buffer = Vec::new(); 272 | encode_message(&mut buffer, &MESSAGE); 273 | encode_message(&mut buffer, &MESSAGE); 274 | 275 | const SPLIT: usize = ENCODED_MESSAGE_SIZE * 2 / 3; 276 | let (first, second) = buffer.split_at(SPLIT); 277 | 278 | let mut decoder = Decoder::default(); 279 | 280 | let mut times_called = 0; 281 | decoder.decode(&first, |_decoded| { 282 | // Should not be called 283 | times_called += 1; 284 | }); 285 | 286 | assert_eq!(0, times_called); 287 | assert_eq!(SPLIT, decoder.stored.len()); 288 | 289 | decoder.decode(&second, |decoded| { 290 | times_called += 1; 291 | assert_eq!(MESSAGE, decoded); 292 | }); 293 | 294 | assert_eq!(2, times_called); 295 | assert_eq!(0, decoder.stored.len()); 296 | } 297 | 298 | #[test] 299 | // [ 1B ][ 1B ][...][ 1B ] 300 | // [ message ] 301 | fn decode_byte_per_byte() { 302 | let mut buffer = Vec::new(); 303 | encode_message(&mut buffer, &MESSAGE); 304 | 305 | let mut decoder = Decoder::default(); 306 | 307 | let mut times_called = 0; 308 | for i in 0..buffer.len() { 309 | decoder.decode(&buffer[i..i + 1], |decoded| { 310 | assert_eq!(buffer.len() - 1, i); 311 | times_called += 1; 312 | assert_eq!(MESSAGE, decoded); 313 | }); 314 | 315 | if i < buffer.len() - 1 { 316 | assert_eq!(i + 1, decoder.stored.len()); 317 | } 318 | } 319 | 320 | assert_eq!(0, decoder.stored.len()); 321 | assert_eq!(1, times_called); 322 | } 323 | 324 | #[test] 325 | // [ 1B ][ remaining ] 326 | // [ message ] 327 | fn decode_message_after_non_enough_padding() { 328 | let msg = [0; 1000]; 329 | let mut buffer = Vec::new(); 330 | encode_message(&mut buffer, &msg); 331 | 332 | let (start_1b, remaining) = buffer.split_at(2); 333 | 334 | let mut decoder = Decoder::default(); 335 | 336 | let mut times_called = 0; 337 | decoder.decode(&start_1b, |_decoded| { 338 | // Should not be called 339 | times_called += 1; 340 | }); 341 | 342 | assert_eq!(0, times_called); 343 | assert_eq!(2, decoder.stored.len()); 344 | 345 | decoder.decode(&remaining, |decoded| { 346 | times_called += 1; 347 | assert_eq!(msg, decoded); 348 | }); 349 | 350 | assert_eq!(1, times_called); 351 | assert_eq!(0, decoder.stored.len()); 352 | } 353 | 354 | #[test] 355 | // [ 1B ][ 1B ][ remaining ] 356 | // [ message ] 357 | fn decode_message_var_size_in_two_data() { 358 | let msg = [0; 1000]; 359 | let mut buffer = Vec::new(); 360 | encode_message(&mut buffer, &msg); 361 | 362 | let (start_1b, remaining) = buffer.split_at(1); 363 | 364 | let mut decoder = Decoder::default(); 365 | 366 | let mut times_called = 0; 367 | decoder.decode(&start_1b, |_decoded| { 368 | // Should not be called 369 | times_called += 1; 370 | }); 371 | 372 | assert_eq!(0, times_called); 373 | assert_eq!(1, decoder.stored.len()); 374 | 375 | let (next_1b, remaining) = remaining.split_at(1); 376 | 377 | let mut times_called = 0; 378 | decoder.decode(&next_1b, |_decoded| { 379 | // Should not be called 380 | times_called += 1; 381 | }); 382 | 383 | assert_eq!(0, times_called); 384 | assert_eq!(2, decoder.stored.len()); 385 | 386 | decoder.decode(&remaining, |decoded| { 387 | times_called += 1; 388 | assert_eq!(msg, decoded); 389 | }); 390 | 391 | assert_eq!(1, times_called); 392 | assert_eq!(0, decoder.stored.len()); 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel::{self, Sender, Receiver, select}; 2 | 3 | use std::time::{Instant, Duration}; 4 | use std::collections::{BTreeMap}; 5 | 6 | /// As a shortcut, it returns the sender and receiver queue as a tuple. 7 | /// 8 | /// Equivalent to: 9 | /// ``` 10 | /// struct MyEvent; // or usually an enum 11 | /// 12 | /// use message_io::events::EventReceiver; 13 | /// 14 | /// let event_queue = EventReceiver::::default(); 15 | /// let event_sender = event_queue.sender().clone(); 16 | /// ``` 17 | pub fn split() -> (EventSender, EventReceiver) { 18 | let event_queue = EventReceiver::default(); 19 | let event_sender = event_queue.sender().clone(); 20 | 21 | (event_sender, event_queue) 22 | } 23 | 24 | /// An ID that represents a timer scheduled. 25 | /// It can be used to cancel the event. 26 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 27 | pub struct TimerId(Instant); 28 | 29 | // Internal enum to enqueue different timer commands in a single queue 30 | enum TimerCommand { 31 | Create(E), 32 | Cancel, 33 | } 34 | 35 | /// A generic and synchronized queue where the user can send and receive events. 36 | /// See [`EventSender`] to see how send events. 37 | /// This entity can be used as an utility for the [`crate::network`] module redirecting the 38 | /// network events to process them later from here. 39 | pub struct EventReceiver { 40 | event_sender: EventSender, // Should be before receiver in order to drop first. 41 | receiver: Receiver, 42 | timer_receiver: Receiver<(Instant, TimerCommand)>, 43 | priority_receiver: Receiver, 44 | timers: BTreeMap, 45 | } 46 | 47 | impl Default for EventReceiver 48 | where E: Send + 'static 49 | { 50 | /// Creates a new event queue for generic incoming events. 51 | fn default() -> Self { 52 | let (sender, receiver) = crossbeam_channel::unbounded(); 53 | let (timer_sender, timer_receiver) = crossbeam_channel::unbounded(); 54 | let (priority_sender, priority_receiver) = crossbeam_channel::unbounded(); 55 | EventReceiver { 56 | event_sender: EventSender::new(sender, timer_sender, priority_sender), 57 | receiver, 58 | timer_receiver, 59 | priority_receiver, 60 | timers: BTreeMap::new(), 61 | } 62 | } 63 | } 64 | 65 | impl EventReceiver 66 | where E: Send + 'static 67 | { 68 | /// Returns the internal sender reference to this queue. 69 | /// This reference can be safety cloned and shared to other threads 70 | /// in order to get several senders to the same queue. 71 | pub fn sender(&self) -> &EventSender { 72 | &self.event_sender 73 | } 74 | 75 | fn enque_timers(&mut self) { 76 | for timer in self.timer_receiver.try_iter() { 77 | match timer.1 { 78 | TimerCommand::Create(e) => self.timers.insert(timer.0, e), 79 | TimerCommand::Cancel => self.timers.remove(&timer.0), 80 | }; 81 | } 82 | } 83 | 84 | /// Blocks the current thread until an event is received by this queue. 85 | pub fn receive(&mut self) -> E { 86 | self.enque_timers(); 87 | // Since [`EventReceiver`] always has a sender attribute, 88 | // any call to [`receive()`] always has a living sender in that time 89 | // and the channel never can be considered disconnected. 90 | if !self.priority_receiver.is_empty() { 91 | self.priority_receiver.recv().unwrap() 92 | } 93 | else if self.timers.is_empty() { 94 | select! { 95 | recv(self.receiver) -> event => event.unwrap(), 96 | recv(self.priority_receiver) -> event => event.unwrap(), 97 | } 98 | } 99 | else { 100 | let next_instant = *self.timers.iter().next().unwrap().0; 101 | if next_instant <= Instant::now() { 102 | self.timers.remove(&next_instant).unwrap() 103 | } 104 | else { 105 | select! { 106 | recv(self.receiver) -> event => event.unwrap(), 107 | recv(self.priority_receiver) -> event => event.unwrap(), 108 | recv(crossbeam_channel::at(next_instant)) -> _ => { 109 | self.timers.remove(&next_instant).unwrap() 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | /// Blocks the current thread until an event is received by this queue or timeout is exceeded. 117 | /// If timeout is reached a None is returned, otherwise the event is returned. 118 | pub fn receive_timeout(&mut self, timeout: Duration) -> Option { 119 | self.enque_timers(); 120 | 121 | if !self.priority_receiver.is_empty() { 122 | Some(self.priority_receiver.recv().unwrap()) 123 | } 124 | else if self.timers.is_empty() { 125 | select! { 126 | recv(self.receiver) -> event => Some(event.unwrap()), 127 | recv(self.priority_receiver) -> event => Some(event.unwrap()), 128 | default(timeout) => None 129 | } 130 | } 131 | else { 132 | let next_instant = *self.timers.iter().next().unwrap().0; 133 | if next_instant <= Instant::now() { 134 | self.timers.remove(&next_instant) 135 | } 136 | else { 137 | select! { 138 | recv(self.receiver) -> event => Some(event.unwrap()), 139 | recv(self.priority_receiver) -> event => Some(event.unwrap()), 140 | recv(crossbeam_channel::at(next_instant)) -> _ => { 141 | self.timers.remove(&next_instant) 142 | } 143 | default(timeout) => None 144 | } 145 | } 146 | } 147 | } 148 | 149 | /// Attempts to receive an event without blocking. 150 | /// Returns Some(E) if an event was received by this queue, otherwise returns None. 151 | pub fn try_receive(&mut self) -> Option { 152 | self.enque_timers(); 153 | 154 | if let Ok(priority_event) = self.priority_receiver.try_recv() { 155 | return Some(priority_event); 156 | } 157 | else if let Some(next_instant) = self.timers.iter().next() { 158 | if *next_instant.0 <= Instant::now() { 159 | let instant = *next_instant.0; 160 | return self.timers.remove(&instant); 161 | } 162 | } 163 | else if let Ok(event) = self.receiver.try_recv() { 164 | return Some(event); 165 | } 166 | 167 | None 168 | } 169 | } 170 | 171 | /// Struct used to send events into a [`EventReceiver`]. 172 | /// This type can only be generated by the receiver `EventReceiver`. 173 | pub struct EventSender { 174 | sender: Sender, 175 | timer_sender: Sender<(Instant, TimerCommand)>, 176 | priority_sender: Sender, 177 | } 178 | 179 | impl EventSender 180 | where E: Send + 'static 181 | { 182 | fn new( 183 | sender: Sender, 184 | timer_sender: Sender<(Instant, TimerCommand)>, 185 | priority_sender: Sender, 186 | ) -> EventSender { 187 | EventSender { sender, timer_sender, priority_sender } 188 | } 189 | 190 | /// Send instantly an event to the event queue. 191 | pub fn send(&self, event: E) { 192 | self.sender.send(event).ok(); 193 | } 194 | 195 | /// Send instantly an event that would be process before any other event sent 196 | /// by the [`EventSender::send()`] method. 197 | /// Successive calls to send_with_priority will maintain the order of arrival. 198 | pub fn send_with_priority(&self, event: E) { 199 | self.priority_sender.send(event).ok(); 200 | } 201 | 202 | /// Send a timed event to the [`EventReceiver`]. 203 | /// The event only will be sent after the specific duration, never before. 204 | /// If the [`EventSender`] is dropped, the event will be generated as well unless 205 | /// [`EventSender::cancel_timer()`] be called. 206 | pub fn send_with_timer(&self, event: E, duration: Duration) -> TimerId { 207 | let when = Instant::now() + duration; 208 | self.timer_sender.send((when, TimerCommand::Create(event))).ok(); 209 | TimerId(when) 210 | } 211 | 212 | /// Remove a timer previously sent by [`EventSender::send_with_timer()`]. 213 | /// The timer will not be receive by the [`EventReceiver`]. 214 | pub fn cancel_timer(&self, timer_id: TimerId) { 215 | self.timer_sender.send((timer_id.0, TimerCommand::Cancel)).ok(); 216 | } 217 | } 218 | 219 | impl Clone for EventSender 220 | where E: Send + 'static 221 | { 222 | fn clone(&self) -> Self { 223 | EventSender::new( 224 | self.sender.clone(), 225 | self.timer_sender.clone(), 226 | self.priority_sender.clone(), 227 | ) 228 | } 229 | } 230 | 231 | #[cfg(test)] 232 | mod tests { 233 | use super::*; 234 | 235 | // This high delay is for ensure to works CI machines that offers really slow resources. 236 | // For a estandar execution, a value of 1ms is enough for the 99% of cases. 237 | const DELAY: u64 = 2000; //ms 238 | 239 | lazy_static::lazy_static! { 240 | static ref ZERO_MS: Duration = Duration::from_millis(0); 241 | static ref TIMER_TIME: Duration = Duration::from_millis(100); 242 | static ref TIMEOUT: Duration = *TIMER_TIME * 2 + Duration::from_millis(DELAY); 243 | } 244 | 245 | #[test] 246 | fn waiting_timer_event() { 247 | let mut queue = EventReceiver::default(); 248 | queue.sender().send_with_timer("Timed", *TIMER_TIME); 249 | assert_eq!(queue.receive_timeout(*TIMEOUT).unwrap(), "Timed"); 250 | } 251 | 252 | #[test] 253 | fn standard_events_order() { 254 | let mut queue = EventReceiver::default(); 255 | queue.sender().send("first"); 256 | queue.sender().send("second"); 257 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "first"); 258 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "second"); 259 | } 260 | 261 | #[test] 262 | fn priority_events_order() { 263 | let mut queue = EventReceiver::default(); 264 | queue.sender().send("standard"); 265 | queue.sender().send_with_priority("priority_first"); 266 | queue.sender().send_with_priority("priority_second"); 267 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "priority_first"); 268 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "priority_second"); 269 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "standard"); 270 | } 271 | 272 | #[test] 273 | fn timer_events_order() { 274 | let mut queue = EventReceiver::default(); 275 | queue.sender().send_with_timer("timed_last", *TIMER_TIME * 2); 276 | queue.sender().send_with_timer("timed_short", *TIMER_TIME); 277 | 278 | std::thread::sleep(*TIMEOUT); 279 | // The timed event has been received at this point 280 | 281 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "timed_short"); 282 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "timed_last"); 283 | } 284 | 285 | #[test] 286 | fn default_and_timer_events_order() { 287 | let mut queue = EventReceiver::default(); 288 | queue.sender().send_with_timer("timed", *TIMER_TIME); 289 | queue.sender().send("standard_first"); 290 | queue.sender().send("standard_second"); 291 | 292 | std::thread::sleep(*TIMEOUT); 293 | // The timed event has been received at this point 294 | 295 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "timed"); 296 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "standard_first"); 297 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "standard_second"); 298 | } 299 | 300 | #[test] 301 | fn priority_and_timer_events_order() { 302 | let mut queue = EventReceiver::default(); 303 | queue.sender().send_with_timer("timed", *TIMER_TIME); 304 | queue.sender().send_with_priority("priority"); 305 | 306 | std::thread::sleep(*TIMEOUT); 307 | // The timed event has been received at this point 308 | 309 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "priority"); 310 | assert_eq!(queue.receive_timeout(*ZERO_MS).unwrap(), "timed"); 311 | } 312 | 313 | #[test] 314 | fn drop_queue_before_sender() { 315 | let queue = EventReceiver::<()>::default(); 316 | let sender = queue.sender().clone(); 317 | drop(queue); 318 | drop(sender); 319 | } 320 | 321 | #[test] 322 | fn standard_events_order_try_receive() { 323 | let mut queue = EventReceiver::default(); 324 | queue.sender().send("first"); 325 | queue.sender().send("second"); 326 | assert_eq!(queue.try_receive().unwrap(), "first"); 327 | assert_eq!(queue.try_receive().unwrap(), "second"); 328 | assert_eq!(queue.try_receive(), None); 329 | } 330 | 331 | #[test] 332 | fn priority_events_order_try_receive() { 333 | let mut queue = EventReceiver::default(); 334 | queue.sender().send("standard"); 335 | queue.sender().send_with_priority("priority_first"); 336 | queue.sender().send_with_priority("priority_second"); 337 | assert_eq!(queue.try_receive().unwrap(), "priority_first"); 338 | assert_eq!(queue.try_receive().unwrap(), "priority_second"); 339 | assert_eq!(queue.try_receive().unwrap(), "standard"); 340 | assert_eq!(queue.try_receive(), None); 341 | } 342 | 343 | #[test] 344 | fn timer_events_order_try_receive() { 345 | let mut queue = EventReceiver::default(); 346 | queue.sender().send_with_timer("timed_last", *TIMER_TIME * 2); 347 | queue.sender().send_with_timer("timed_short", *TIMER_TIME); 348 | 349 | assert_eq!(queue.try_receive(), None); 350 | std::thread::sleep(*TIMER_TIME); 351 | // The timed event has been received at this point 352 | assert_eq!(queue.try_receive().unwrap(), "timed_short"); 353 | std::thread::sleep(*TIMER_TIME); 354 | assert_eq!(queue.try_receive().unwrap(), "timed_last"); 355 | assert_eq!(queue.try_receive(), None); 356 | } 357 | 358 | #[test] 359 | fn default_and_timer_events_order_try_receive() { 360 | let mut queue = EventReceiver::default(); 361 | queue.sender().send_with_timer("timed", *TIMER_TIME); 362 | queue.sender().send("standard_first"); 363 | queue.sender().send("standard_second"); 364 | 365 | std::thread::sleep(*TIMEOUT); 366 | // The timed event has been received at this point 367 | 368 | assert_eq!(queue.try_receive().unwrap(), "timed"); 369 | assert_eq!(queue.try_receive().unwrap(), "standard_first"); 370 | assert_eq!(queue.try_receive().unwrap(), "standard_second"); 371 | assert_eq!(queue.try_receive(), None); 372 | } 373 | 374 | #[test] 375 | fn priority_and_timer_events_order_try_receive() { 376 | let mut queue = EventReceiver::default(); 377 | queue.sender().send_with_timer("timed", *TIMER_TIME); 378 | queue.sender().send_with_priority("priority"); 379 | 380 | std::thread::sleep(*TIMEOUT); 381 | // The timed event has been received at this point 382 | 383 | assert_eq!(queue.try_receive().unwrap(), "priority"); 384 | assert_eq!(queue.try_receive().unwrap(), "timed"); 385 | assert_eq!(queue.try_receive(), None); 386 | } 387 | 388 | #[test] 389 | fn cancel_timers() { 390 | let mut queue = EventReceiver::default(); 391 | let id = queue.sender().send_with_timer("timed", *TIMER_TIME); 392 | queue.sender().cancel_timer(id); 393 | 394 | std::thread::sleep(*TIMEOUT); 395 | // The timed event has been received at this point, but was cancelled. 396 | 397 | assert_eq!(queue.try_receive(), None); 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | use message_io::network::{NetEvent, Transport, SendStatus}; 2 | use message_io::node::{self, NodeEvent}; 3 | use message_io::util::thread::{NamespacedThread}; 4 | use message_io::adapters::udp::{self}; 5 | 6 | use test_case::test_case; 7 | 8 | use rand::{SeedableRng, Rng}; 9 | 10 | use std::collections::{HashSet}; 11 | use std::net::{SocketAddr}; 12 | use std::time::{Duration}; 13 | 14 | const LOCAL_ADDR: &'static str = "127.0.0.1:0"; 15 | const MIN_MESSAGE: &'static [u8] = &[42]; 16 | const SMALL_MESSAGE: &'static str = "Integration test message"; 17 | const BIG_MESSAGE_SIZE: usize = 1024 * 1024 * 8; // 8MB 18 | 19 | lazy_static::lazy_static! { 20 | pub static ref TIMEOUT: Duration = Duration::from_secs(60); 21 | pub static ref TIMEOUT_SMALL: Duration = Duration::from_secs(1); 22 | } 23 | 24 | // Common error messages 25 | const TIMEOUT_EVENT_RECV_ERR: &'static str = "Timeout, but an event was expected."; 26 | 27 | mod util { 28 | use std::sync::{Once}; 29 | 30 | // Used to init the log only one time for all tests; 31 | static INIT: Once = Once::new(); 32 | 33 | #[allow(dead_code)] 34 | pub enum LogThread { 35 | Enabled, 36 | Disabled, 37 | } 38 | 39 | #[allow(dead_code)] 40 | pub fn init_logger(log_thread: LogThread) { 41 | INIT.call_once(|| configure_logger(log_thread).unwrap()); 42 | } 43 | 44 | fn configure_logger(log_thread: LogThread) -> Result<(), fern::InitError> { 45 | fern::Dispatch::new() 46 | .filter(|metadata| metadata.target().starts_with("message_io")) 47 | .format(move |out, message, record| { 48 | let thread_name = format!("[{}]", std::thread::current().name().unwrap()); 49 | out.finish(format_args!( 50 | "[{}][{}][{}]{} {}", 51 | chrono::Local::now().format("%M:%S:%f"), // min:sec:nano 52 | record.level(), 53 | record.target().strip_prefix("message_io::").unwrap_or(record.target()), 54 | if let LogThread::Enabled = log_thread { thread_name } else { String::new() }, 55 | message, 56 | )) 57 | }) 58 | .chain(std::io::stdout()) 59 | .apply()?; 60 | Ok(()) 61 | } 62 | } 63 | 64 | #[allow(unused_imports)] 65 | use util::{LogThread}; 66 | 67 | fn start_echo_server( 68 | transport: Transport, 69 | expected_clients: usize, 70 | ) -> (NamespacedThread<()>, SocketAddr) { 71 | let (tx, rx) = crossbeam_channel::bounded(1); 72 | let thread = NamespacedThread::spawn("test-server", move || { 73 | let mut messages_received = 0; 74 | let mut disconnections = 0; 75 | let mut clients = HashSet::new(); 76 | 77 | let (node, listener) = node::split(); 78 | node.signals().send_with_timer((), *TIMEOUT); 79 | 80 | let (listener_id, server_addr) = node.network().listen(transport, LOCAL_ADDR).unwrap(); 81 | tx.send(server_addr).unwrap(); 82 | 83 | listener.for_each(move |event| match event { 84 | NodeEvent::Signal(_) => panic!("{}", TIMEOUT_EVENT_RECV_ERR), 85 | NodeEvent::Network(net_event) => match net_event { 86 | NetEvent::Connected(..) => unreachable!(), 87 | NetEvent::Accepted(endpoint, id) => { 88 | assert_eq!(listener_id, id); 89 | match transport.is_connection_oriented() { 90 | true => assert!(clients.insert(endpoint)), 91 | false => unreachable!(), 92 | } 93 | } 94 | NetEvent::Message(endpoint, data) => { 95 | assert_eq!(MIN_MESSAGE, data); 96 | 97 | let status = node.network().send(endpoint, &data); 98 | assert_eq!(SendStatus::Sent, status); 99 | 100 | messages_received += 1; 101 | 102 | if !transport.is_connection_oriented() { 103 | // We assume here that if the protocol is not 104 | // connection-oriented it will no create a resource. 105 | // The remote will be managed from the listener resource 106 | assert_eq!(listener_id, endpoint.resource_id()); 107 | if messages_received == expected_clients { 108 | node.stop() //Exit from thread. 109 | } 110 | } 111 | } 112 | NetEvent::Disconnected(endpoint) => { 113 | match transport.is_connection_oriented() { 114 | true => { 115 | disconnections += 1; 116 | assert!(clients.remove(&endpoint)); 117 | if disconnections == expected_clients { 118 | assert_eq!(expected_clients, messages_received); 119 | assert_eq!(0, clients.len()); 120 | node.stop() //Exit from thread. 121 | } 122 | } 123 | false => unreachable!(), 124 | } 125 | } 126 | }, 127 | }); 128 | }); 129 | 130 | let server_addr = rx.recv_timeout(*TIMEOUT).expect(TIMEOUT_EVENT_RECV_ERR); 131 | (thread, server_addr) 132 | } 133 | 134 | fn start_echo_client_manager( 135 | transport: Transport, 136 | server_addr: SocketAddr, 137 | clients_number: usize, 138 | ) -> NamespacedThread<()> { 139 | NamespacedThread::spawn("test-client", move || { 140 | let (node, listener) = node::split(); 141 | node.signals().send_with_timer((), *TIMEOUT); 142 | 143 | let mut clients = HashSet::new(); 144 | let mut received = 0; 145 | 146 | for _ in 0..clients_number { 147 | node.network().connect(transport, server_addr).unwrap(); 148 | } 149 | 150 | listener.for_each(move |event| match event { 151 | NodeEvent::Signal(_) => panic!("{}", TIMEOUT_EVENT_RECV_ERR), 152 | NodeEvent::Network(net_event) => match net_event { 153 | NetEvent::Connected(server, status) => { 154 | assert!(status); 155 | let status = node.network().send(server, MIN_MESSAGE); 156 | assert_eq!(SendStatus::Sent, status); 157 | assert!(clients.insert(server)); 158 | } 159 | NetEvent::Message(endpoint, data) => { 160 | assert!(clients.remove(&endpoint)); 161 | assert_eq!(MIN_MESSAGE, data); 162 | node.network().remove(endpoint.resource_id()); 163 | 164 | received += 1; 165 | if received == clients_number { 166 | node.stop(); //Exit from thread. 167 | } 168 | } 169 | NetEvent::Accepted(..) => unreachable!(), 170 | NetEvent::Disconnected(_) => unreachable!(), 171 | }, 172 | }); 173 | }) 174 | } 175 | 176 | fn start_burst_receiver( 177 | transport: Transport, 178 | expected_count: usize, 179 | ) -> (NamespacedThread<()>, SocketAddr) { 180 | let (tx, rx) = crossbeam_channel::bounded(1); 181 | let thread = NamespacedThread::spawn("test-receiver", move || { 182 | let (node, listener) = node::split(); 183 | node.signals().send_with_timer((), *TIMEOUT); 184 | 185 | let (_, receiver_addr) = node.network().listen(transport, LOCAL_ADDR).unwrap(); 186 | tx.send(receiver_addr).unwrap(); 187 | 188 | let mut count = 0; 189 | listener.for_each(move |event| match event { 190 | NodeEvent::Signal(_) => panic!("{}", TIMEOUT_EVENT_RECV_ERR), 191 | NodeEvent::Network(net_event) => match net_event { 192 | NetEvent::Connected(..) => unreachable!(), 193 | NetEvent::Accepted(..) => (), 194 | NetEvent::Message(_, data) => { 195 | let expected_message = format!("{}: {}", SMALL_MESSAGE, count); 196 | assert_eq!(expected_message, String::from_utf8_lossy(&data)); 197 | count += 1; 198 | if count == expected_count { 199 | node.stop(); 200 | } 201 | } 202 | NetEvent::Disconnected(_) => (), 203 | }, 204 | }); 205 | }); 206 | (thread, rx.recv().unwrap()) 207 | } 208 | 209 | fn start_burst_sender( 210 | transport: Transport, 211 | receiver_addr: SocketAddr, 212 | expected_count: usize, 213 | ) -> NamespacedThread<()> { 214 | NamespacedThread::spawn("test-sender", move || { 215 | let (node, listener) = node::split::<()>(); 216 | 217 | let (receiver, _) = node.network().connect(transport, receiver_addr).unwrap(); 218 | 219 | let mut count = 0; 220 | listener.for_each(move |event| match event { 221 | NodeEvent::Signal(_) => { 222 | if count < expected_count { 223 | let message = format!("{}: {}", SMALL_MESSAGE, count); 224 | let status = node.network().send(receiver, message.as_bytes()); 225 | assert_eq!(SendStatus::Sent, status); 226 | 227 | count += 1; 228 | if !transport.is_connection_oriented() { 229 | // We need a rate to not lose packet. 230 | node.signals().send_with_timer((), Duration::from_micros(50)); 231 | } 232 | else { 233 | node.signals().send(()); 234 | } 235 | } 236 | else { 237 | node.stop(); 238 | } 239 | } 240 | NodeEvent::Network(net_event) => match net_event { 241 | NetEvent::Connected(_, status) => { 242 | assert!(status); 243 | node.signals().send(()); 244 | } 245 | NetEvent::Disconnected(_) => (), 246 | _ => unreachable!(), 247 | }, 248 | }); 249 | }) 250 | } 251 | 252 | #[cfg_attr(feature = "tcp", test_case(Transport::Tcp, 1))] 253 | #[cfg_attr(feature = "tcp", test_case(Transport::Tcp, 100))] 254 | #[cfg_attr(feature = "tcp", test_case(Transport::FramedTcp, 1))] 255 | #[cfg_attr(feature = "tcp", test_case(Transport::FramedTcp, 100))] 256 | #[cfg_attr(feature = "udp", test_case(Transport::Udp, 1))] 257 | #[cfg_attr(feature = "udp", test_case(Transport::Udp, 100))] 258 | #[cfg_attr(feature = "websocket", test_case(Transport::Ws, 1))] 259 | #[cfg_attr(feature = "websocket", test_case(Transport::Ws, 100))] 260 | // NOTE: A medium-high `clients` value can exceeds the "open file" limits of an OS in CI 261 | // with an obfuscated error message. 262 | fn echo(transport: Transport, clients: usize) { 263 | //util::init_logger(LogThread::Enabled); // Enable it for better debugging 264 | 265 | let (_server_thread, server_addr) = start_echo_server(transport, clients); 266 | let _client_thread = start_echo_client_manager(transport, server_addr, clients); 267 | } 268 | 269 | // Tcp: Does not apply: it's stream based 270 | #[cfg_attr(feature = "udp", test_case(Transport::Udp, 2000))] 271 | #[cfg_attr(feature = "tcp", test_case(Transport::FramedTcp, 200000))] 272 | #[cfg_attr(feature = "websocket", test_case(Transport::Ws, 200000))] 273 | fn burst(transport: Transport, messages_count: usize) { 274 | //util::init_logger(LogThread::Enabled); // Enable it for better debugging 275 | 276 | let (_receiver_thread, server_addr) = start_burst_receiver(transport, messages_count); 277 | let _sender_thread = start_burst_sender(transport, server_addr, messages_count); 278 | } 279 | 280 | #[cfg_attr(feature = "tcp", test_case(Transport::Tcp, BIG_MESSAGE_SIZE))] 281 | #[cfg_attr(feature = "tcp", test_case(Transport::FramedTcp, BIG_MESSAGE_SIZE))] 282 | #[cfg_attr(feature = "udp", test_case(Transport::Udp, udp::MAX_LOCAL_PAYLOAD_LEN))] 283 | #[cfg_attr(feature = "websocket", test_case(Transport::Ws, BIG_MESSAGE_SIZE))] 284 | fn message_size(transport: Transport, message_size: usize) { 285 | //util::init_logger(LogThread::Disabled); // Enable it for better debugging 286 | 287 | assert!(message_size <= transport.max_message_size()); 288 | 289 | let mut rng = rand::rngs::StdRng::seed_from_u64(42); 290 | let sent_message: Vec = (0..message_size).map(|_| rng.random()).collect(); 291 | 292 | let (node, listener) = node::split(); 293 | node.signals().send_with_timer((), *TIMEOUT); 294 | 295 | let (_, receiver_addr) = node.network().listen(transport, LOCAL_ADDR).unwrap(); 296 | let (receiver, _) = node.network().connect(transport, receiver_addr).unwrap(); 297 | 298 | let mut _async_sender: Option> = None; 299 | let mut received_message = Vec::new(); 300 | 301 | listener.for_each(move |event| match event { 302 | NodeEvent::Signal(_) => panic!("{}", TIMEOUT_EVENT_RECV_ERR), 303 | NodeEvent::Network(net_event) => match net_event { 304 | NetEvent::Connected(endpoint, status) => { 305 | assert!(status); 306 | assert_eq!(receiver, endpoint); 307 | 308 | let node = node.clone(); 309 | let sent_message = sent_message.clone(); 310 | 311 | // Protocols as TCP blocks the sender if the receiver is not reading data 312 | // and its buffer is fill. 313 | _async_sender = Some(NamespacedThread::spawn("test-sender", move || { 314 | let status = node.network().send(receiver, &sent_message); 315 | assert_eq!(status, SendStatus::Sent); 316 | assert!(node.network().remove(receiver.resource_id())); 317 | })); 318 | } 319 | NetEvent::Accepted(..) => (), 320 | NetEvent::Message(_, data) => { 321 | if transport.is_packet_based() { 322 | received_message = data.to_vec(); 323 | assert_eq!(sent_message, received_message); 324 | node.stop(); 325 | } 326 | else { 327 | received_message.extend_from_slice(&data); 328 | } 329 | } 330 | NetEvent::Disconnected(_) => { 331 | assert_eq!(sent_message.len(), received_message.len()); 332 | assert_eq!(sent_message, received_message); 333 | node.stop(); 334 | } 335 | }, 336 | }); 337 | } 338 | 339 | #[cfg(target_os = "macos")] 340 | #[ignore = "macos requires to enable permission for multicast"] 341 | #[test] 342 | fn multicast_reuse_addr() { 343 | //util::init_logger(LogThread::Disabled); // Enable it for better debugging 344 | 345 | let (node, listener) = node::split(); 346 | node.signals().send_with_timer((), *TIMEOUT_SMALL); 347 | 348 | let multicast_addr = "239.255.0.1:3015"; 349 | let (id_1, _) = node.network().listen(Transport::Udp, multicast_addr).unwrap(); 350 | let (id_2, _) = node.network().listen(Transport::Udp, multicast_addr).unwrap(); 351 | 352 | let (target, local_addr) = node.network().connect(Transport::Udp, multicast_addr).unwrap(); 353 | 354 | let mut received = 0; 355 | listener.for_each(move |event| match event { 356 | NodeEvent::Signal(_) => panic!("{}", TIMEOUT_EVENT_RECV_ERR), 357 | NodeEvent::Network(net_event) => match net_event { 358 | NetEvent::Connected(endpoint, status) => { 359 | assert!(status); 360 | assert_eq!(endpoint, target); 361 | let status = node.network().send(target, &[42]); 362 | assert_eq!(status, SendStatus::Sent); 363 | } 364 | NetEvent::Message(endpoint, data) => { 365 | if endpoint.resource_id() != id_1 { 366 | assert_eq!(endpoint.resource_id(), id_2); 367 | } 368 | if endpoint.resource_id() != id_2 { 369 | assert_eq!(endpoint.resource_id(), id_1); 370 | } 371 | assert_eq!(data, [42]); 372 | assert_eq!(endpoint.addr(), local_addr); 373 | 374 | received += 1; 375 | if received == 2 { 376 | node.stop(); 377 | } 378 | } 379 | NetEvent::Accepted(..) => unreachable!(), 380 | NetEvent::Disconnected(_) => unreachable!(), 381 | }, 382 | }); 383 | } 384 | --------------------------------------------------------------------------------