
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>
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;
int err =
io::bind(server_socket, server_addr);
auto client_address = io::socket::make_address<sockaddr_in>();
auto [client_socket, addr] =
io::accept(server_socket, 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>
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);
if (result == 0) {
const char *message = "Hello, server!";
}
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);
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.