Switchy TCP
Generic TCP networking abstraction with Tokio and simulator support.
Overview
The TCP package provides:
- Generic TCP Traits: Abstract TCP listener and stream interfaces
- Tokio Integration: Production-ready async TCP networking
- Simulator Support: Mock TCP networking for testing
- Stream Splitting: Read/write half separation for concurrent operations
- Address Handling: Local and peer address access
- Type Safety: Generic traits for different TCP implementations
Features
Generic TCP Interface
- GenericTcpListener: Abstract TCP listener trait
- GenericTcpStream: Abstract TCP stream trait with read/write
- GenericTcpStreamReadHalf: Abstract read-only stream interface
- GenericTcpStreamWriteHalf: Abstract write-only stream interface
Implementation Support
- Tokio TCP: Production async TCP networking
- Simulator TCP: Mock networking for testing and development
- Wrapper Types: Type-safe wrappers for different implementations
Stream Operations
- AsyncRead/AsyncWrite: Tokio async I/O trait implementations
- Stream Splitting: Separate read and write operations
- Address Information: Access to local and peer socket addresses
- Connection Management: Connect, accept, and close operations
Installation
Add this to your Cargo.toml:
[dependencies]
switchy_tcp = { path = "../tcp" }
switchy_tcp = {
path = "../tcp",
default-features = false,
features = ["tokio"]
}
Usage
Basic TCP Server
use switchy_tcp::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
println!("Server listening on 127.0.0.1:8080");
loop {
let (mut stream, addr) = listener.accept().await?;
println!("New connection from: {}", addr);
tokio::spawn(async move {
if let Err(e) = handle_connection(stream).await {
eprintln!("Connection error: {}", e);
}
});
}
}
async fn handle_connection(mut stream: TcpStream) -> Result<(), Box<dyn std::error::Error>> {
let mut buffer = [0; 1024];
loop {
let n = stream.read(&mut buffer).await?;
if n == 0 {
break; }
stream.write_all(&buffer[..n]).await?;
}
Ok(())
}
TCP Client
use switchy_tcp::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut stream = TcpStream::connect("127.0.0.1:8080").await?;
println!("Connected to: {}", stream.peer_addr()?);
println!("Local address: {}", stream.local_addr()?);
stream.write_all(b"Hello, server!").await?;
let mut buffer = [0; 1024];
let n = stream.read(&mut buffer).await?;
println!("Server response: {}", String::from_utf8_lossy(&buffer[..n]));
Ok(())
}
Stream Splitting
use switchy_tcp::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
async fn handle_bidirectional_stream(stream: TcpStream) -> Result<(), Box<dyn std::error::Error>> {
let (mut read_half, mut write_half) = stream.into_split();
let reader_handle = tokio::spawn(async move {
let mut buffer = [0; 1024];
loop {
match read_half.read(&mut buffer).await {
Ok(0) => break, Ok(n) => {
println!("Received: {}", String::from_utf8_lossy(&buffer[..n]));
}
Err(e) => {
eprintln!("Read error: {}", e);
break;
}
}
}
});
let writer_handle = tokio::spawn(async move {
let messages = ["Hello", "World", "Goodbye"];
for msg in messages {
if let Err(e) = write_half.write_all(msg.as_bytes()).await {
eprintln!("Write error: {}", e);
break;
}
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
});
let _ = tokio::try_join!(reader_handle, writer_handle)?;
Ok(())
}
Generic TCP Usage
use switchy_tcp::{GenericTcpListener, GenericTcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
async fn generic_server<S, R, W, L>(listener: L) -> Result<(), switchy_tcp::Error>
where
S: GenericTcpStream<R, W>,
R: switchy_tcp::GenericTcpStreamReadHalf,
W: switchy_tcp::GenericTcpStreamWriteHalf,
L: GenericTcpListener<S>,
{
loop {
let (mut stream, addr) = listener.accept().await?;
println!("Connection from: {}", addr);
tokio::spawn(async move {
let mut buffer = [0; 1024];
if let Ok(n) = stream.read(&mut buffer).await {
let _ = stream.write_all(&buffer[..n]).await;
}
});
}
}
Simulator Mode (Testing)
#[cfg(feature = "simulator")]
use switchy_tcp::simulator::{TcpListener, TcpStream};
#[tokio::test]
async fn test_tcp_communication() {
let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();
let client_stream = TcpStream::connect("127.0.0.1:8080").await.unwrap();
let (server_stream, _) = listener.accept().await.unwrap();
}
Generic Traits
GenericTcpListener
use switchy_tcp::{GenericTcpListener, Error};
use std::net::SocketAddr;
use async_trait::async_trait;
#[async_trait]
pub trait GenericTcpListener<T>: Send + Sync {
async fn accept(&self) -> Result<(T, SocketAddr), Error>;
}
GenericTcpStream
use switchy_tcp::{GenericTcpStream, GenericTcpStreamReadHalf, GenericTcpStreamWriteHalf};
use tokio::io::{AsyncRead, AsyncWrite};
use std::net::SocketAddr;
pub trait GenericTcpStream<R: GenericTcpStreamReadHalf, W: GenericTcpStreamWriteHalf>:
AsyncRead + AsyncWrite + Send + Sync + Unpin
{
fn into_split(self) -> (R, W);
fn local_addr(&self) -> std::io::Result<SocketAddr>;
fn peer_addr(&self) -> std::io::Result<SocketAddr>;
}
Error Handling
use switchy_tcp::{Error, TcpListener, TcpStream};
async fn handle_tcp_errors() {
match TcpListener::bind("invalid-address").await {
Ok(listener) => {
}
Err(Error::IO(io_err)) => {
eprintln!("Listener I/O error: {}", io_err);
}
Err(Error::AddrParse(parse_err)) => {
eprintln!("Listener address parse error: {}", parse_err);
}
Err(Error::ParseInt(int_err)) => {
eprintln!("Listener integer parse error: {}", int_err);
}
#[cfg(feature = "simulator")]
Err(Error::Send) => {
eprintln!("Listener simulator send error");
}
}
match TcpStream::connect("invalid-address").await {
Ok(stream) => {
}
Err(io_err) => {
eprintln!("Stream I/O error: {}", io_err);
}
}
}
Feature Flags
tokio: Enable Tokio-based TCP implementation (enabled by default)
simulator: Enable simulator/mock TCP implementation (enabled by default)
Note: Both features are enabled by default. When both are enabled, the simulator type aliases (TcpListener, TcpStream, etc.) are used. To use Tokio types exclusively, disable default features and enable only tokio.
Type Aliases
When features are enabled, convenient type aliases are available:
pub type TcpListener = SimulatorTcpListener;
pub type TcpStream = SimulatorTcpStream;
pub type TcpStreamReadHalf = SimulatorTcpStreamReadHalf;
pub type TcpStreamWriteHalf = SimulatorTcpStreamWriteHalf;
pub type TcpListener = TokioTcpListener;
pub type TcpStream = TokioTcpStream;
pub type TcpStreamReadHalf = TokioTcpStreamReadHalf;
pub type TcpStreamWriteHalf = TokioTcpStreamWriteHalf;
To access specific implementations when both features are enabled:
use switchy_tcp::tokio::{TcpListener as TokioTcpListener, TcpStream as TokioTcpStream};
use switchy_tcp::simulator::{TcpListener as SimTcpListener, TcpStream as SimTcpStream};
Dependencies
Core dependencies:
- switchy_async: Async runtime abstraction with I/O, macros, sync, time, and util support
- tokio: Networking primitives (required, with
net feature)
- async-trait: Async trait support
- thiserror: Error handling
- paste: Macro utilities
- log: Logging
Simulator-specific dependencies (when simulator feature is enabled):
- bytes: Byte buffer management
- flume: MPSC channel implementation
- scoped-tls: Thread-local storage for simulator context
Use Cases
- Network Servers: TCP-based server applications
- Client Applications: TCP client connections
- Protocol Implementation: Custom network protocol development
- Testing: Mock network communication in tests
- Cross-platform Networking: Abstract over different TCP implementations
- Microservices: Service-to-service TCP communication