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]
tcp = { path = "../tcp" }
tcp = {
path = "../tcp",
features = ["tokio", "simulator"]
}
Usage
Basic TCP Server (Tokio)
#[cfg(feature = "tokio")]
use 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
#[cfg(feature = "tokio")]
use 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
#[cfg(feature = "tokio")]
use 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 tcp::{GenericTcpListener, GenericTcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
async fn generic_server<S, R, W, L>(listener: L) -> Result<(), tcp::Error>
where
S: GenericTcpStream<R, W>,
R: tcp::GenericTcpStreamReadHalf,
W: 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 tcp::simulator::{TcpListener, TcpStream};
#[tokio::test]
async fn test_tcp_communication() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_stream = TcpStream::connect(&addr.to_string()).await.unwrap();
let (server_stream, _) = listener.accept().await.unwrap();
}
Generic Traits
GenericTcpListener
use tcp::{GenericTcpListener, Error};
use std::net::SocketAddr;
#[async_trait]
pub trait GenericTcpListener<T>: Send + Sync {
async fn accept(&self) -> Result<(T, SocketAddr), Error>;
}
GenericTcpStream
use 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 tcp::{Error, TcpStream};
async fn handle_tcp_errors() {
match TcpStream::connect("invalid-address").await {
Ok(stream) => {
}
Err(Error::IO(io_err)) => {
eprintln!("I/O error: {}", io_err);
}
Err(Error::AddrParse(parse_err)) => {
eprintln!("Address parse error: {}", parse_err);
}
Err(Error::ParseInt(int_err)) => {
eprintln!("Integer parse error: {}", int_err);
}
#[cfg(feature = "simulator")]
Err(Error::Send) => {
eprintln!("Simulator send error");
}
}
}
Feature Flags
tokio
: Enable Tokio-based TCP implementation
simulator
: Enable simulator/mock TCP implementation
Type Aliases
When features are enabled, convenient type aliases are available:
pub type TcpListener = TokioTcpListener;
pub type TcpStream = TokioTcpStream;
pub type TcpStreamReadHalf = TokioTcpStreamReadHalf;
pub type TcpStreamWriteHalf = TokioTcpStreamWriteHalf;
pub type TcpListener = SimulatorTcpListener;
pub type TcpStream = SimulatorTcpStream;
pub type TcpStreamReadHalf = SimulatorTcpStreamReadHalf;
pub type TcpStreamWriteHalf = SimulatorTcpStreamWriteHalf;
Dependencies
- Tokio: Async runtime and I/O (optional)
- Async-trait: Async trait support
- Thiserror: Error handling
- Paste: Macro utilities
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