simple, fast socket-based event loop
Find a file
2025-11-04 10:40:38 -06:00
examples fixes and optimizations 2025-11-04 10:40:38 -06:00
src fixes and optimizations 2025-11-04 10:40:38 -06:00
.gitignore initial commit 2025-11-03 09:05:15 -06:00
Cargo.lock initial commit 2025-11-03 09:05:15 -06:00
Cargo.toml initial commit 2025-11-03 09:05:15 -06:00
IMPLEMENTATION.md initial commit 2025-11-03 09:05:15 -06:00
README.md initial commit 2025-11-03 09:05:15 -06:00
STYLE.md initial commit 2025-11-03 09:05:15 -06:00

sockkets

A zero-dependency sockets library for Rust, inspired by uSockets. Provides high-performance event-driven I/O with cross-platform support for epoll (Linux), kqueue (BSD/macOS), and poll (POSIX fallback).

Features

  • Zero Dependencies: Only uses libc for system call bindings - no external runtime dependencies
  • Cross-Platform:
    • Linux: epoll backend with edge-triggered mode
    • macOS/BSD: kqueue backend
    • Other POSIX: poll fallback
  • Event-Driven: Efficient event loop with callback-based API
  • Protocol Support: TCP (listener/stream) and UDP sockets
  • Shared Buffer: 512KB shared receive buffer (like uSockets)
  • Timer System: Two-tier timeout tracking (4-second and 1-minute granularity)
  • Priority Queue: Budget-based low-priority task scheduling

Architecture

sockkets follows the uSockets design philosophy:

  1. BSD Socket Abstraction: Platform-agnostic socket operations
  2. Backend Abstraction: Unified interface for epoll/kqueue/poll
  3. Event Loop: Single-threaded, non-blocking event processing
  4. Context-Based Callbacks: Group sockets with shared handlers
  5. Efficient I/O: Shared receive buffer, edge-triggered events

Usage

TCP Echo Server

use sockkets::{EventLoop, TcpListener};
use std::os::unix::io::RawFd;

fn on_open(_fd: RawFd) -> Option<*mut ()> {
    println!("New connection!");
    None
}

fn on_data(fd: RawFd, data: &[u8], _user_data: Option<*mut ()>) -> usize {
    // Echo data back
    unsafe {
        libc::write(fd, data.as_ptr() as *const libc::c_void, data.len());
    }
    data.len()
}

fn on_close(fd: RawFd, error: i32, _user_data: Option<*mut ()>) {
    println!("Connection closed: fd={}, error={}", fd, error);
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut event_loop = EventLoop::default()?;

    let context_id = event_loop
        .contexts_mut()
        .create()
        .on_open(on_open)
        .on_data(on_data)
        .on_close(on_close)
        .build();

    let listener = TcpListener::bind("127.0.0.1:8080")?;
    listener.register_with_loop(&mut event_loop, context_id)?;

    event_loop.run()?;
    Ok(())
}

UDP Echo Server

use sockkets::{EventLoop, UdpSocket};

fn on_data(fd: RawFd, data: &[u8], _user_data: Option<*mut ()>) -> usize {
    println!("Received: {}", String::from_utf8_lossy(data));
    data.len()
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut event_loop = EventLoop::default()?;

    let context_id = event_loop
        .contexts_mut()
        .create()
        .on_data(on_data)
        .build();

    let socket = UdpSocket::bind("127.0.0.1:8080")?;
    socket.register_with_loop(&mut event_loop, context_id)?;

    event_loop.run()?;
    Ok(())
}

API Overview

Event Loop

  • EventLoop::default() - Create with platform-default backend
  • EventLoop::new() - Create with specific backend type
  • run() - Run until stop() is called
  • run_once(timeout) - Process events for one iteration
  • stop() - Stop the event loop

Callbacks

  • on_open(fd) -> Option<*mut ()> - New connection accepted/established
  • on_data(fd, data, user_data) -> usize - Data received (returns bytes consumed)
  • on_writable(fd, user_data) - Socket is writable
  • on_close(fd, error, user_data) - Socket closed
  • on_timeout(fd, user_data) - Socket timed out

Socket Types

  • TcpListener: Accept incoming TCP connections
  • TcpStream: Connected TCP socket
  • UdpSocket: UDP datagram socket

Design Principles

Inspired by uSockets:

  1. Minimal Abstraction: Direct mapping to system calls where possible
  2. Zero Copy: Shared receive buffer, minimize allocations
  3. Callback-Based: Simple, predictable control flow
  4. Edge-Triggered: Efficient event notification
  5. No Dependencies: Self-contained implementation

Building

cargo build
cargo build --example tcp_echo
cargo build --example udp_echo

Running Examples

# TCP echo server
cargo run --example tcp_echo

# UDP echo server
cargo run --example udp_echo

# Test with nc:
nc localhost 8080

Project Structure

src/
├── lib.rs              # Public API exports
├── error.rs            # Error types
├── platform.rs         # Platform detection
├── backend/            # Event loop backends
│   ├── mod.rs          # Backend trait
│   ├── epoll.rs        # Linux epoll
│   ├── kqueue.rs       # BSD/macOS kqueue
│   └── poll.rs         # POSIX poll fallback
├── socket/             # Socket abstractions
│   ├── mod.rs          # Socket management
│   ├── bsd.rs          # BSD socket layer
│   ├── tcp.rs          # TCP sockets
│   └── udp.rs          # UDP sockets
├── loop/               # Event loop
│   ├── mod.rs          # Main loop implementation
│   └── priority.rs     # Priority queue
├── context.rs          # Callback contexts
└── timer.rs            # Timer wheel

Future Enhancements

  • TLS/SSL integration
  • Async/await API alongside callback API
  • Unix domain sockets
  • Zero-copy sendfile support
  • HTTP/WebSocket protocol helpers
  • More sophisticated backpressure handling

License

This project is a demonstration implementation for educational purposes.

Comparison with uSockets

Feature uSockets (C) sockkets (Rust)
Language C Rust
Dependencies 0 1 (libc - just FFI)
Backends epoll, kqueue, IOCP epoll, kqueue, poll
API Style Callbacks Callbacks
Buffer Size 512KB shared 512KB shared
Timers 4s/60s dual 4s/60s dual
TLS µTLS Planned

Acknowledgments

Inspired by the excellent uSockets library by Alex Hultman.