Expand description
A userspace TCP stack that runs over any ReadWrite transport.
jktcp is intentionally simplified: it targets reliable, ordered transports
(e.g. CDTunnel, a Unix socket carrying raw IP frames) where real packet loss is
rare. It still implements stop-and-wait retransmission with exponential back-off
so that transient glitches do not silently drop data.
§Two usage patterns
§1. AdapterStream — single-threaded, lifetime-bound
stream::AdapterStream holds a &mut reference to the adapter::Adapter,
so it can only be used from the same task that owns the adapter. One stream is
alive at a time; while it exists the adapter is exclusively borrowed.
Choose this when you open exactly one connection, drive it entirely from a single async task, and do not need to move the stream across tasks or store it on the heap alongside other things that reference the adapter.
use jktcp::adapter::Adapter;
use jktcp::stream::AdapterStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut adapter = Adapter::new(Box::new(transport), todo!(), todo!());
let mut stream = AdapterStream::connect(&mut adapter, 1234).await?;
stream.write_all(b"hello").await?;
let mut buf = [0u8; 32];
let n = stream.read(&mut buf).await?;
println!("received: {:?}", &buf[..n]);
stream.close().await?;§2. AdapterHandle / StreamHandle — multi-threaded, 'static
handle::AdapterHandle spawns the adapter’s I/O loop onto the Tokio runtime.
All interaction happens through channels, so the resulting handle::StreamHandle
is Send + Sync + 'static and can be stored in Arc, passed across tasks, or
boxed as a trait object. Multiple streams can be open simultaneously.
Choose this when any of the following apply:
- You need to open more than one connection on the same adapter.
- The stream must cross an
asynctask boundary (e.g.tokio::spawn). - You store the stream in a struct alongside other owned data (no lifetime parameter on the struct).
- You expose the stream through an interface that requires
'staticbounds (FFI, trait objects, etc.).
use jktcp::adapter::Adapter;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let adapter = Adapter::new(Box::new(transport), todo!(), todo!());
let mut handle = adapter.to_async_handle();
let mut stream = handle.connect(1234).await?;
// stream is Send + 'static — spawn it freely
tokio::spawn(async move {
stream.write_all(b"hello").await.unwrap();
let mut buf = [0u8; 32];
stream.read(&mut buf).await.unwrap();
});§Retransmission behaviour
Both paths share the same adapter::Adapter logic:
- Data is sent stop-and-wait: a second segment is not put on the wire until the first is acknowledged.
- If no ACK arrives within the RTO (200 ms initially, doubling on each retry), the segment is retransmitted.
- After 5 failed attempts the connection is closed with
ErrorKind::TimedOut. - Out-of-order segments (sequence number ahead of expected) are dropped silently; duplicate segments (already acknowledged) are re-ACKed.
§PCAP capture
Call adapter::Adapter::pcap (or handle::AdapterHandle::pcap) with a
file path before connecting to write a .pcap file that Wireshark can open.
Modules§
- adapter
- Core TCP state machine.
- handle
- Thread-safe handle over a background-task
crate::adapter::Adapter. - packets
- stream
- Lifetime-bound stream over a borrowed
Adapter.
Traits§
- Read
Write - A marker trait for types that can act as the underlying transport.