AsyncBerkeley
Asynchronous Berkeley sockets. Simple.
Loading...
Searching...
No Matches
AsyncBerkeley

Tests Codacy Badge Codacy Badge

AsyncBerkeley is a modern header-only C++ socket I/O library that provides an asynchronous implementation of (most of) the Berkeley Sockets API. It is built using NVIDIA's stdexec library and aims to drop stdexec as a dependency once there is adequate compiler support for C++26's std::execution. AsyncBerkeley aims to make asynchronous socket I/O as simple as the ubiquitous Berkeley sockets API.

Features

  • Asynchronous Execution Framework: Sender/receiver pattern implementation with a simple poll based multiplexer for scalable I/O operations.
  • Familiar Berkeley Sockets API: Socket operations include: accept, bind, connect, fcntl, getpeername, getsockname, getsockopt, listen, recvmsg, sendmsg, setsockopt, and shutdown with plans to add support for send, sendto, recv, and recvfrom.
  • Full Support for Asynchronous Scatter/Gather Socket I/O.: sendmsg and recvmsg API support scatter/gather I/O using the native iovec, and msghdr buffer structures.
  • Convenient socket helper classes for RAII: Helpers like socket_dialog, socket_handle, socket_option, and socket_address provide RAII and a convenient C++20 std::span based API for working with native sockets and socket addresses.

Contributing

All contributions are welcome! AsyncBerkeley aims to be a library where people can learn network programming, as well as C++'s std::execution. Please open a Github issue if you have a question or send me an email.

Example Applications

Some simple examples of applications build with AsyncBerkeley:

Quick Start

Prerequisites

  • CMake 3.28+
  • C++20 compatible compiler
  • NVIDIA stdexec: Auto-fetched via CPM for sender/receiver execution patterns
  • GoogleTest (Optional): Auto-fetched via CMake FetchContent for unit testing
  • GoogleBenchmark (Optional): Auto-fetched via CMake FetchContent for running benchmarks.
  • Boost.ASIO (Optional): Boost libraries are needed to compile the benchmarks.

Build and Install with Cmake

# Clone the repository
git clone https://github.com/kcexn/async-berkeley.git
cd async-berkeley
# Build with cmake.
cmake --preset release
cmake --build build/release
# Install with cmake.
cmake --install build/release
# Uninstall with cmake
cmake --build build/release -t uninstall

Detailed Build Instructions

For comprehensive build instructions, dependency installation guides, code coverage setup, and troubleshooting, see DEVELOPER.md.

Documentation

API documentation is available at: https://kcexn.github.io/async-berkeley/

To build documentation locally:

cmake --preset release -DIO_ENABLE_DOCS=ON
# Generate docs in build/release/docs
cmake --build build/release --t docs
# Deploy docs to top-level docs directory.
cmake --build build/release --t docs-deploy
# Open docs/html/index.hml in a browser.

Usage

Basic Socket Operations

#include <io.hpp>
#include <netinet/in.h>
// Create a RAII socket handle
io::socket::socket_handle server_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Create socket address using the new socket_option-based API
auto server_addr = io::socket::make_address<sockaddr_in>();
server_addr->sin_family = AF_INET;
server_addr->sin_addr.s_addr = INADDR_ANY;
server_addr->sin_port = 0; // Let system choose port
// Bind using the socket_address wrapper
int err = io::bind(server_socket, server_addr);
// Start listening
err = io::listen(server_socket, 5);
auto client_address = io::socket::make_address<sockaddr_in>();
// Accept incoming connections - high-level API returns managed objects
auto [client_socket, addr] = io::accept(server_socket, client_address);
// Some socket types (like UNIX domain sockets) may return
// a differently sized address from accept. In this
// case, we will need to assign the returned addr back
// to client_address.
if(client_address != addr)
client_address = addr;
A thread-safe, move-only RAII wrapper for a native socket handle.
Definition socket_handle.hpp:42
auto listen(auto &&socket, int backlog) -> decltype(auto)
Sets a socket to listen for incoming connections.
Definition customization.hpp:193
auto accept(auto &&socket, std::span< std::byte > address={}) -> decltype(auto)
Accepts an incoming connection on a listening socket.
Definition customization.hpp:93
auto bind(auto &&socket, std::span< const std::byte > address) -> decltype(auto)
Binds a socket to a local address.
Definition customization.hpp:106

Client Socket Connection

#include <io.hpp>
#include <netinet/in.h>
#include <arpa/inet.h>
// Create client socket
io::socket::socket_handle client_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// Create server address from a native sockaddr structure.
struct sockaddr_in native_addr{};
native_addr.sin_family = AF_INET;
native_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
native_addr.sin_port = htons(8080);
auto server_addr = io::socket::make_address(&native_addr);
// Connect using socket_address
int result = io::connect(client_socket, server_addr);
if (result == 0) {
// Connected successfully - send/receive data
const char *message = "Hello, server!";
msg.buffers.emplace_back(message, strlen(message));
io::sendmsg(client_socket, msg, 0);
}
constexpr auto emplace_back(Args &&...args) -> decltype(auto)
Constructs a native buffer in-place at the end of the collection.
auto sendmsg(auto &&socket, auto &&msg, int flags) -> decltype(auto)
Sends a message on a socket.
Definition customization.hpp:222
auto connect(auto &&socket, std::span< const std::byte > address) -> decltype(auto)
Connects a socket to a remote address.
Definition customization.hpp:120
auto make_address(const Addr *addr=nullptr) -> socket_address< Addr >
Creates a socket_address from a socket address structure.
Definition socket_address.hpp:80
Represents a complete socket message.
Definition socket_message.hpp:156
message_type buffers
Buffers for scatter/gather I/O.
Definition socket_message.hpp:174

Asynchronous Socket Operations

The library provides full asynchronous socket operations using the sender/receiver pattern with stdexec. Here is a snippet based on the tcp echo example in examples/:

void writer(async_scope &scope, const dialog &client,
const std::shared_ptr<message> &msg,
const std::shared_ptr<buffer> &buf)
{
auto send_sendmsg = sendmsg(client, *msg, 0) |
then([=, &scope](auto len) {
if (msg->buffers += len)
return writer(scope, client, msg, buf);
reader(scope, client);
}) |
upon_error(error_handler);
scope.spawn(std::move(send_sendmsg));
}
void reader(async_scope &scope, const dialog &client)
{
auto msg = std::make_shared<message>();
auto buf = std::make_shared<buffer>(1024);
msg->buffers.push_back(*buf);
auto send_recvmsg = recvmsg(client, *msg, 0) |
then([=, &scope](auto len) {
if (!len)
return;
writer(scope, client, msg, buf);
}) |
upon_error(error_handler);
scope.spawn(std::move(send_recvmsg));
}
void acceptor(async_scope &scope, const dialog &server)
{
auto send_accept = accept(server) | then([&, server](auto result) {
auto [client, addr] = std::move(result);
reader(scope, client);
acceptor(scope, server);
}) |
upon_error(error_handler);
scope.spawn(std::move(send_accept));
}
void make_server(async_scope &scope, const dialog &server)
{
auto server_address = make_address<sockaddr_in>();
server_address->sin_family = AF_INET;
server_address->sin_addr.s_addr = inet_addr("127.0.0.1");
server_address->sin_port = htons(8080);
bind(server, server_address);
listen(server, SOMAXCONN);
acceptor(scope, server);
}
int main(int argc, char *argv[])
{
async_scope scope;
triggers trigs;
make_server(scope, trigs.emplace(AF_INET, SOCK_STREAM, IPPROTO_TCP));
while (trigs.wait());
return 0;
}
auto recvmsg(auto &&socket, auto &&msg, int flags) -> decltype(auto)
Receives a message from a socket.
Definition customization.hpp:207

Limitations

  • Windows Support: Windows support is currently planned but very far from complete.
  • I/O Multiplexer Support: Only the poll multiplexer is currently supported. There are plans to support io_uring, kqueue, and Windows' IOCP at some stage in the future.
  • Integration point support: Currently the I/O multiplexer can only be used with socket_handle's or socket_dialog's. Support for arbitrary file descriptors so that third-party libraries can be integrated into the I/O loop is planned for some stage in the future.
  • Complete Berkeley Sockets API support: recv, recvfrom, send, and sendto are not yet supported.