1
0
Fork 0
Simplified, minimal socket wrapper.
Find a file
2025-11-02 14:02:49 -06:00
examples Preconfigured Tcp and Udp classes to build sockets 2025-10-23 14:43:03 -05:00
src kqueue backend, initial tests 2025-11-02 11:59:09 -06:00
tests tests, comparisons, todo 2025-11-02 14:02:49 -06:00
.gitignore tests, comparisons, todo 2025-11-02 14:02:49 -06:00
README.md Preconfigured Tcp and Udp classes to build sockets 2025-10-23 14:43:03 -05:00
TODO.md tests, comparisons, todo 2025-11-02 14:02:49 -06:00

Sharkk Sockets

A succinct, header-only C++ socket library with multi-backend support. Abstracts kernel-level IO APIs (epoll, kqueue, io_uring, Windows IOCP) behind a clean, unified interface for embedding in event loops and other projects.

Features

  • Single-header library - Just include sharkk.hpp
  • Multiple backends - Automatic platform detection with manual override
    • Linux: epoll (default), io_uring (high-performance async I/O)
    • BSD/macOS: kqueue (future)
    • Windows: IOCP (future)
    • Universal: POSIX fallback
  • Clean, intuitive API - Sane defaults with fluent builder pattern
    • TcpSocket and UdpSocket come pre-configured (TCP/UDP over IPv4)
    • No need to specify .tcp() or .ipv4() - just focus on your logic
  • Railway-Oriented error handling - Type-safe Result<T> instead of exceptions
  • Zero-cost abstractions - Header-only with compile-time polymorphism
  • RAII socket management - Automatic cleanup, move-only semantics
  • Event loop friendly - Easy integration via native_handle()
  • Modern C++ - Requires C++14 or later

Quick Start

#include "src/sharkk.hpp"
using namespace sharkk;

// TCP Server - clean and simple with sane defaults!
auto server = TcpSocket::create()
	.reuseaddr()
	.nonblocking()
	.build()
	.expect("Failed to create socket");

server.bind(Address::any_ipv4(8080)).unwrap();
server.listen(128).unwrap();

auto client = server.accept().unwrap();

// TCP Client
auto sock = TcpSocket::create()
	.nodelay()
	.build()
	.unwrap();

auto addr = Address::resolve("example.com", 80).unwrap();
sock.connect(addr).unwrap();
sock.send("GET / HTTP/1.1\r\n\r\n", 18);

Architecture

File Structure

src/
  sharkk.hpp                  # Main header (include this)
  socket.hpp                  # Socket class with Builder
  detail/
    platform.hpp              # Platform detection
    error.hpp                 # Result<T> error handling
    address.hpp               # Address abstraction
  backends/
    backend_traits.hpp        # Backend interface
    posix_backend.hpp         # POSIX baseline (universal)
    epoll_backend.hpp         # Linux epoll (default)
    iouring_backend.hpp       # Linux io_uring (high-performance)
    kqueue_backend.hpp        # BSD/macOS (future)
    iocp_backend.hpp          # Windows IOCP (future)

examples/
  echo_server.cpp             # TCP echo server (default backend)
  http_client.cpp             # Simple HTTP client
  iouring_echo_server.cpp     # io_uring echo server example

Backend Selection

The library automatically detects your platform and selects the best backend:

  • Linux: epoll by default, or io_uring for high-performance async I/O
  • BSD/macOS: kqueue (future implementation)
  • Windows: IOCP (future implementation)
  • Other: POSIX fallback (universal compatibility)

To use io_uring (Linux 5.1+):

#define SHARKK_USE_IOURING
#include "sharkk.hpp"
// Link with: -luring

Manual backend override:

#define SHARKK_USE_EPOLL    // Force epoll (Linux default)
#define SHARKK_USE_IOURING  // Force io_uring (requires liburing)
#define SHARKK_USE_KQUEUE   // Force kqueue (BSD/macOS)
#define SHARKK_USE_IOCP     // Force IOCP (Windows)
#define SHARKK_USE_POSIX    // Force POSIX (universal fallback)
#include "sharkk.hpp"

API Reference

Socket Class

The main interface for socket operations.

Note: Use TcpSocket or UdpSocket - they come with sane defaults (TCP/UDP over IPv4). For custom backends, use Socket<YourBackend> directly.

Builder Pattern Construction

// TcpSocket comes pre-configured for TCP over IPv4
TcpSocket::create()
	.nonblocking()   // or .blocking()
	.reuseaddr()
	.reuseport()
	.nodelay()	   // TCP_NODELAY (disable Nagle)
	.keepalive()	 // SO_KEEPALIVE
	.build();		// Returns Result<TcpSocket>

// For IPv6, just add .ipv6()
TcpSocket::create()
	.ipv6()
	.build();

// UdpSocket is pre-configured for UDP over IPv4
UdpSocket::create()
	.reuseaddr()
	.build();

Direct Construction

TcpSocket sock(AF_INET, SOCK_STREAM, IPPROTO_TCP);
TcpSocket sock(existing_fd, true);  // Wrap existing fd

Connection Operations

Result<void> connect(const Address& addr);
Result<void> bind(const Address& addr);
Result<void> listen(int backlog = 128);
Result<Socket> accept();
Result<Socket> accept(Address& peer_addr);

I/O Operations

Result<size_t> send(const void* data, size_t len, int flags = 0);
Result<size_t> recv(void* buffer, size_t len, int flags = 0);
Result<size_t> send_all(const void* data, size_t len);  // Handles partial sends
Result<size_t> recv_exact(void* buffer, size_t len);	// Receives exact amount

Configuration (Fluent)

Socket& set_nonblocking(bool enabled = true);
Socket& set_blocking();
Socket& set_reuseaddr(bool enabled = true);
Socket& set_reuseport(bool enabled = true);
Socket& set_nodelay(bool enabled = true);
Socket& set_keepalive(bool enabled = true);

Queries

bool is_valid() const;
Result<Address> local_address() const;
Result<Address> peer_address() const;

Shutdown

Result<void> shutdown_read();
Result<void> shutdown_write();
Result<void> shutdown_both();
Result<void> close();  // Manual close (automatic in destructor)

Event Loop Integration

int fd = sock.native_handle();  // Get raw file descriptor
// Register fd with epoll/kqueue/io_uring/etc.

int fd = sock.release();  // Release ownership (no auto-close)

Backend Information

static const char* backend_name();
static bool supports_zerocopy();
static bool supports_batching();

Address Class

Wrapper around sockaddr_in and sockaddr_in6.

// Factory methods
Address::ipv4("127.0.0.1", 8080);
Address::ipv6("::1", 8080);
Address::resolve("example.com", 80);  // DNS resolution
Address::any_ipv4(8080);			  // 0.0.0.0:8080
Address::any_ipv6(8080);			  // [::]:8080
Address::localhost_ipv4(8080);		// 127.0.0.1:8080
Address::localhost_ipv6(8080);		// [::1]:8080

// Queries
int family() const;				   // AF_INET or AF_INET6
bool is_ipv4() const;
bool is_ipv6() const;
bool is_valid() const;
uint16_t port() const;
std::string ip() const;
std::string to_string() const;		// "192.168.1.1:8080"

// Raw access (for backend use)
const sockaddr* data() const;
socklen_t size() const;

Result<T> Type

Railway-Oriented error handling (inspired by Rust's Result<T, E>).

Result<TcpSocket> result = TcpSocket::create().tcp().build();

// Check status
if (result.is_ok()) { /* ... */ }
if (result.is_error()) { /* ... */ }
if (result) { /* ... */ }  // Implicit bool conversion

// Access value
TcpSocket sock = result.unwrap();					// Throws on error
TcpSocket sock = result.value_or(default_socket);	// With default
TcpSocket sock = result.expect("Custom message");	// Throws with message

// Access error
const Error& err = result.error();
int code = result.error_code();
std::string msg = result.error_message();

// Monadic operations
result.map([](Socket& s) { return s.native_handle(); });
result.and_then([](Socket& s) { return s.connect(addr); });
result.or_else([](const Error& e) { return fallback(); });

Error Handling

struct Error {
	int code;
	std::string message;

	// Factory methods
	static Error from_errno(int err = errno);
	static Error invalid_argument(const std::string& msg);
	static Error not_connected();
	static Error would_block();

	// Queries
	bool is_would_block() const;
	bool is_interrupted() const;
};

Platform Information

// Compile-time
namespace sharkk::platform {
	constexpr const char* name;	   // "Linux", "macOS", etc.
	constexpr const char* backend;	// "epoll", "kqueue", etc.
	constexpr bool is_linux;
	constexpr bool is_macos;
	constexpr bool has_epoll;
	constexpr bool has_iouring;
}

// Runtime
PlatformInfo info = sharkk::get_platform_info();
std::cout << "Platform: " << info.name << std::endl;
std::cout << "Backend: " << info.backend << std::endl;

Examples

TCP Echo Server

#include "src/sharkk.hpp"
#include <iostream>
using namespace sharkk;

int main() {
	auto server = TcpSocket::create()
		.reuseaddr()
		.nonblocking()
		.build()
		.expect("Failed to create socket");

	server.bind(Address::any_ipv4(8080)).expect("Bind failed");
	server.listen(128).expect("Listen failed");

	std::cout << "Listening on port 8080" << std::endl;

	while (true) {
		auto client_result = server.accept();
		if (!client_result) {
			if (client_result.error().is_would_block()) {
				usleep(1000);  // No clients ready
				continue;
			}
			std::cerr << "Accept error: " << client_result.error_message() << std::endl;
			continue;
		}

		auto client = std::move(client_result.unwrap());
		client.set_nonblocking();

		char buffer[4096];
		auto recv_result = client.recv(buffer, sizeof(buffer));
		if (recv_result) {
			size_t n = recv_result.unwrap();
			client.send_all(buffer, n);  // Echo back
		}
	}

	return 0;
}

TCP Client

#include "src/sharkk.hpp"
#include <iostream>
using namespace sharkk;

int main() {
	auto sock = TcpSocket::create()
		.nodelay()
		.build()
		.expect("Failed to create socket");

	auto addr = Address::resolve("example.com", 80)
		.expect("Failed to resolve");

	sock.connect(addr).expect("Failed to connect");

	const char* request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
	sock.send_all(request, strlen(request));

	char buffer[4096];
	auto result = sock.recv(buffer, sizeof(buffer));
	if (result) {
		std::cout.write(buffer, result.unwrap());
	}

	return 0;
}

Event Loop Integration

// Get raw file descriptor for epoll/kqueue/etc.
int fd = sock.native_handle();

// Example with Linux epoll
int epoll_fd = epoll_create1(0);
epoll_event ev;
ev.events = EPOLLIN | EPOLLET;  // Edge-triggered
ev.data.fd = fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);

// Poll for events
epoll_event events[10];
int nfds = epoll_wait(epoll_fd, events, 10, -1);

for (int i = 0; i < nfds; i++) {
	if (events[i].data.fd == fd) {
		// Socket is ready for I/O
		sock.recv(buffer, size);
	}
}

High-Performance with io_uring

#define SHARKK_USE_IOURING
#include "src/sharkk.hpp"
#include <iostream>
using namespace sharkk;

int main() {
	// io_uring backend provides async I/O automatically
	auto server = TcpSocket::create()
		.reuseaddr()
		.nonblocking()
		.build()
		.expect("Failed to create socket");

	server.bind(Address::any_ipv4(8080)).expect("Bind failed");
	server.listen(128).expect("Listen failed");

	std::cout << "io_uring server listening on port 8080" << std::endl;
	std::cout << "Zero-copy: " << (TcpSocket::supports_zerocopy() ? "yes" : "no") << std::endl;
	std::cout << "Batching: " << (TcpSocket::supports_batching() ? "yes" : "no") << std::endl;

	// Accept and handle clients
	while (true) {
		auto client_result = server.accept();
		if (!client_result) {
			if (client_result.error().is_would_block()) {
				usleep(1000);
				continue;
			}
			std::cerr << "Accept error: " << client_result.error_message() << std::endl;
			continue;
		}

		auto client = std::move(client_result.unwrap());
		client.set_nonblocking();

		// Echo data with high-performance io_uring backend
		char buffer[4096];
		while (true) {
			auto recv_result = client.recv(buffer, sizeof(buffer));
			if (!recv_result) break;

			size_t n = recv_result.unwrap();
			if (n == 0) break;  // EOF

			client.send_all(buffer, n);
		}
	}

	return 0;
}

Building Examples

# Compile with default backend (epoll on Linux, POSIX elsewhere)
g++ -std=c++17 -I. -o examples/echo_server examples/echo_server.cpp
g++ -std=c++17 -I. -o examples/http_client examples/http_client.cpp

# Compile with io_uring backend (Linux 5.1+, requires liburing)
g++ -std=c++17 -DSHARKK_USE_IOURING -I. -o examples/iouring_echo_server \
    examples/iouring_echo_server.cpp -luring

# Compile with POSIX backend (universal compatibility)
g++ -std=c++17 -DSHARKK_USE_POSIX -I. -o examples/echo_server examples/echo_server.cpp

# Run examples
./examples/echo_server 8080
./examples/http_client example.com
./examples/iouring_echo_server 8080

Design Philosophy

  1. Simplicity - Clean, intuitive API with fluent builder pattern
  2. Safety - RAII, move-only semantics, type-safe error handling
  3. Performance - Zero-cost abstractions, compile-time polymorphism
  4. Portability - Works across Linux, BSD, macOS, Windows
  5. Flexibility - Easy backend selection, escape hatches for advanced use
  6. Composability - Designed for embedding in event loops

Backend Implementations

POSIX Backend (Universal Fallback)

Standard POSIX socket syscalls. Works on all platforms.

  • socket(), connect(), bind(), listen(), accept()
  • send(), recv()
  • fcntl() for non-blocking mode
  • setsockopt() for socket options

Epoll Backend (Linux Default)

Optimized for Linux with epoll-specific features:

  • accept4() with SOCK_NONBLOCK and SOCK_CLOEXEC
  • MSG_ZEROCOPY for zero-copy sends (Linux 4.14+)
  • MSG_MORE for batching sends
  • TCP optimizations: TCP_QUICKACK, TCP_DEFER_ACCEPT, TCP_FASTOPEN
  • Low-latency: SO_BUSY_POLL, TCP_USER_TIMEOUT

io_uring Backend (Linux 5.1+)

High-performance async I/O implementation:

  • True async I/O - Kernel-level async operations via submission/completion queues
  • Zero-copy support - Reduces memory copies for large transfers
  • Batching - Multiple operations submitted in a single syscall
  • Graceful fallback - Falls back to epoll/POSIX for unsupported operations
  • Thread-local contexts - No locking overhead
  • Requires: Linux 5.1+ kernel and liburing library

To use io_uring:

# Install liburing
sudo apt install liburing-dev  # Debian/Ubuntu
sudo dnf install liburing-devel  # Fedora

# Compile with io_uring support
g++ -std=c++17 -DSHARKK_USE_IOURING -I. -o myapp myapp.cpp -luring

Future Backends

  • kqueue - BSD/macOS event notification
  • IOCP - Windows I/O Completion Ports

Requirements

  • C++14 or later
  • POSIX-compliant OS (Linux, macOS, BSD) or Windows
  • No external dependencies

License

See LICENSE file for details.

Contributing

Contributions welcome! Future work:

  • Add kqueue backend for BSD/macOS
  • Add IOCP backend for Windows
  • Unit tests and integration tests
  • Benchmarks comparing backends (epoll vs io_uring)
  • UDP multicast support
  • Unix domain sockets
  • SSL/TLS wrapper
  • Async/await C++20 coroutine support

Version

Current version: 0.1.0