| examples | ||
| src | ||
| tests | ||
| .gitignore | ||
| README.md | ||
| TODO.md | ||
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
- Linux:
- Clean, intuitive API - Sane defaults with fluent builder pattern
TcpSocketandUdpSocketcome 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:
epollby default, orio_uringfor 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
- Simplicity - Clean, intuitive API with fluent builder pattern
- Safety - RAII, move-only semantics, type-safe error handling
- Performance - Zero-cost abstractions, compile-time polymorphism
- Portability - Works across Linux, BSD, macOS, Windows
- Flexibility - Easy backend selection, escape hatches for advanced use
- 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 modesetsockopt()for socket options
Epoll Backend (Linux Default)
Optimized for Linux with epoll-specific features:
accept4()withSOCK_NONBLOCKandSOCK_CLOEXECMSG_ZEROCOPYfor zero-copy sends (Linux 4.14+)MSG_MOREfor 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