Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
** xref:4.guide/4m.error-handling.adoc[Error Handling]
** xref:4.guide/4n.buffers.adoc[Buffer Sequences]
** xref:4.guide/4o.file-io.adoc[File I/O]
** xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets]
* xref:5.testing/5.intro.adoc[Testing]
** xref:5.testing/5a.mocket.adoc[Mock Sockets]
* xref:benchmark-report.adoc[Benchmarks]
Expand Down
14 changes: 10 additions & 4 deletions doc/modules/ROOT/pages/4.guide/4a.tcp-networking.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,9 @@ UDP includes a checksum covering the header and data. The receiver verifies
the checksum and discards corrupted packets. Unlike TCP, UDP doesn't
retransmit—the data is simply lost.

Corosio currently supports only TCP. UDP may be added in future versions.
Corosio supports TCP, UDP, and Unix domain sockets. See
xref:4p.unix-sockets.adoc[Unix Domain Sockets] for local inter-process
communication without the TCP/IP stack overhead.

== Ports and Sockets

Expand Down Expand Up @@ -699,10 +701,14 @@ listening socket.

Corosio wraps the complexity of TCP programming in a coroutine-friendly API:

* **socket** — Connect to servers, send and receive data
* **tcp_acceptor** — Listen for and accept incoming connections
* **tcp_socket** — Connect to servers, send and receive data over TCP
* **udp_socket** — Send and receive datagrams over UDP
* **tcp_acceptor** — Listen for and accept incoming TCP connections
* **local_stream_socket** — Stream-oriented Unix domain sockets for local IPC
* **local_datagram_socket** — Datagram-oriented Unix domain sockets for local IPC
* **resolver** — Translate hostnames to IP addresses
* **endpoint** — Represent addresses and ports
* **endpoint** — Represent IP addresses and ports
* **local_endpoint** — Represent Unix socket paths

All operations are asynchronous and return awaitables. You don't manage raw
socket handles or deal with platform-specific APIs directly.
Expand Down
267 changes: 267 additions & 0 deletions doc/modules/ROOT/pages/4.guide/4p.unix-sockets.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
//
// Copyright (c) 2026 Michael Vandeberg
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/corosio
//

= Unix Domain Sockets

Unix domain sockets provide inter-process communication (IPC) on the same
machine without going through the TCP/IP network stack. They use filesystem
paths instead of IP addresses and ports, offering lower latency and higher
throughput than loopback TCP.

[NOTE]
====
Code snippets assume:
[source,cpp]
----
#include <boost/corosio/local_stream_socket.hpp>
#include <boost/corosio/local_stream_acceptor.hpp>
#include <boost/corosio/local_datagram_socket.hpp>
#include <boost/corosio/local_socket_pair.hpp>
#include <boost/corosio/local_endpoint.hpp>
#include <boost/capy/buffers.hpp>

namespace corosio = boost::corosio;
namespace capy = boost::capy;
----
====

== When to Use Unix Sockets

Use Unix domain sockets instead of TCP when:

* Both endpoints are on the same machine
* You need lower latency (no TCP/IP stack overhead)
* You need higher throughput for local communication
* You want filesystem-based access control (file permissions on the socket path)

Common use cases include database connections (PostgreSQL, MySQL, Redis),
container networking, and microservice communication on a single host.

== Socket Types

Corosio provides two Unix socket types, mirroring the TCP/UDP split:

[cols="1,1,2"]
|===
| Class | Protocol | Description

| `local_stream_socket`
| `SOCK_STREAM`
| Reliable, ordered byte stream (like TCP). Supports connect/accept.

| `local_datagram_socket`
| `SOCK_DGRAM`
| Message-oriented datagrams (like UDP). Preserves message boundaries.
|===

== Stream Sockets

Stream sockets work like TCP: a server binds and listens on a path, clients
connect, and both sides read and write byte streams.

=== Server (Acceptor)

[source,cpp]
----
capy::task<> server(corosio::io_context& ioc)
{
corosio::local_stream_acceptor acc(ioc);
acc.open();

auto ec = acc.bind(corosio::local_endpoint("/tmp/my_app.sock"));
if (ec) co_return;

ec = acc.listen();
if (ec) co_return;

corosio::local_stream_socket peer(ioc);
auto [accept_ec] = co_await acc.accept(peer);
if (accept_ec) co_return;

// peer is now connected — read and write as with tcp_socket
char buf[1024];
auto [read_ec, n] = co_await peer.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
}
----

The acceptor does **not** automatically remove the socket file on close.
You must `unlink()` the path before binding (if it exists) and after you
are done:

[source,cpp]
----
::unlink("/tmp/my_app.sock"); // remove stale socket
acc.bind(corosio::local_endpoint("/tmp/my_app.sock"));
----

=== Client

[source,cpp]
----
capy::task<> client(corosio::io_context& ioc)
{
corosio::local_stream_socket s(ioc);

// connect() opens the socket automatically
auto [ec] = co_await s.connect(
corosio::local_endpoint("/tmp/my_app.sock"));
if (ec) co_return;

char const msg[] = "hello";
auto [wec, n] = co_await s.write_some(
capy::const_buffer(msg, sizeof(msg)));
}
----

=== Socket Pairs

For bidirectional IPC between a parent and child (or two coroutines),
use `make_local_stream_pair()` which calls the `socketpair()` system call:

[source,cpp]
----
auto [s1, s2] = corosio::make_local_stream_pair(ioc);

// Data written to s1 can be read from s2, and vice versa.
co_await s1.write_some(capy::const_buffer("ping", 4));

char buf[16];
auto [ec, n] = co_await s2.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
// buf contains "ping"
----

This is the fastest way to create a connected pair — it uses a single
`socketpair()` syscall with no filesystem paths involved.

== Datagram Sockets

Datagram sockets preserve message boundaries. Each `send` delivers exactly
one message that the receiver gets as a complete unit from `recv`.

=== Connectionless Mode

Both sides bind to paths, then use `send_to`/`recv_from`:

[source,cpp]
----
corosio::local_datagram_socket s(ioc);
s.open();
s.bind(corosio::local_endpoint("/tmp/my_dgram.sock"));

// Send to a specific peer
co_await s.send_to(
capy::const_buffer("hello", 5),
corosio::local_endpoint("/tmp/peer.sock"));

// Receive from any sender
corosio::local_endpoint sender;
auto [ec, n] = co_await s.recv_from(
capy::mutable_buffer(buf, sizeof(buf)), sender);
----

=== Connected Mode

After calling `connect()`, use `send`/`recv` without specifying the peer:

[source,cpp]
----
auto [s1, s2] = corosio::make_local_datagram_pair(ioc);

co_await s1.send(capy::const_buffer("msg", 3));

auto [ec, n] = co_await s2.recv(
capy::mutable_buffer(buf, sizeof(buf)));
----

== Local Endpoints

Unix socket endpoints use filesystem paths instead of IP+port:

[source,cpp]
----
// Create from a path
corosio::local_endpoint ep("/tmp/my_app.sock");

// Query the path
std::string_view path = ep.path();

// Check if empty (unbound)
bool bound = !ep.empty();
----

The maximum path length is 107 bytes (the `sun_path` field in `sockaddr_un`
minus the null terminator). Paths longer than this throw
`std::errc::filename_too_long`.

=== Abstract Sockets (Linux Only)

On Linux, paths starting with a null byte (`'\0'`) create abstract sockets
that exist in a kernel namespace rather than the filesystem. They don't leave
socket files behind and don't need cleanup:

[source,cpp]
----
// Abstract socket — no file created
corosio::local_endpoint ep(std::string_view("\0/my_app", 8));
assert(ep.is_abstract());
----

== Comparison with TCP

[cols="1,1,1"]
|===
| Feature | TCP (`tcp_socket`) | Unix (`local_stream_socket`)

| Addressing
| IP address + port
| Filesystem path

| Scope
| Network (any machine)
| Local machine only

| Latency
| Higher (TCP/IP stack)
| Lower (kernel shortcut)

| Throughput
| Limited by network stack
| Higher for local IPC

| Access control
| Firewall rules
| File permissions

| DNS resolution
| Yes (via `resolver`)
| No (direct paths)

| Platform
| All platforms
| POSIX only (Linux, macOS, BSD)
|===

== Platform Support

Unix domain sockets are available on all POSIX platforms:

* **Linux** — Full support including abstract sockets
* **macOS** — Full support (no abstract sockets)
* **FreeBSD** — Full support (no abstract sockets)

Windows has limited AF_UNIX support (since Windows 10 1803) but Corosio
does not currently support Unix sockets on Windows.

== Next Steps

* xref:4d.sockets.adoc[TCP Sockets] — TCP socket operations
* xref:4e.tcp-acceptor.adoc[TCP Acceptors] — TCP listener operations
* xref:4f.endpoints.adoc[IP Endpoints] — IP address and port endpoints
12 changes: 12 additions & 0 deletions doc/modules/ROOT/pages/glossary.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ Lazy::
A coroutine or operation that doesn't start until explicitly triggered.
`capy::task` is lazy—it starts when awaited.

Local Endpoint::
A filesystem path used as the address for a Unix domain socket. Represented
by `corosio::local_endpoint`. See xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets].

== M

Mocket::
Expand Down Expand Up @@ -254,6 +258,14 @@ Type Erasure::
Hiding concrete types behind an abstract interface. Enables runtime
polymorphism without templates.

== U

Unix Domain Socket::
A socket that communicates between processes on the same machine using
filesystem paths instead of IP addresses and ports. Available as stream
(`local_stream_socket`) and datagram (`local_datagram_socket`) variants.
See xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets].

== W

Wait::
Expand Down
8 changes: 6 additions & 2 deletions doc/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ the _IoAwaitable_ protocol, ensuring your coroutines resume on the correct
executor without manual dispatch.

* **io_context** — Event loop for processing asynchronous operations
* **socket** — Asynchronous TCP socket with connect, read, and write
* **tcp_socket** — Asynchronous TCP socket with connect, read, and write
* **tcp_acceptor** — TCP listener for accepting incoming connections
* **tcp_server** — Server framework with worker pools
* **udp_socket** — Asynchronous UDP socket for datagrams
* **local_stream_socket** — Unix domain stream socket for local IPC
* **local_datagram_socket** — Unix domain datagram socket for local IPC
* **resolver** — Asynchronous DNS resolution
* **timer** — Asynchronous timer for delays and timeouts
* **signal_set** — Asynchronous signal handling
Expand All @@ -35,7 +38,7 @@ Corosio focuses on coroutine-first I/O primitives. It does not include:
* General-purpose executor abstractions (use Boost.Capy)
* The sender/receiver execution model (P2300)
* HTTP, WebSocket, or other application protocols (use Boost.Http or Boost.Beast2)
* UDP or other transport protocols (TCP only for now)
* UDP multicast or raw sockets

Corosio works with Boost.Capy for task management and execution contexts.

Expand Down Expand Up @@ -154,3 +157,4 @@ int main()
* xref:4.guide/4b.concurrent-programming.adoc[Concurrent Programming] — Coroutines and strands
* xref:4.guide/4c.io-context.adoc[I/O Context] — Understand the event loop
* xref:4.guide/4d.sockets.adoc[Sockets] — Learn socket operations in detail
* xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets] — Local IPC with stream and datagram sockets
10 changes: 10 additions & 0 deletions include/boost/corosio.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,26 @@
#include <boost/corosio/io_context.hpp>
#include <boost/corosio/ipv4_address.hpp>
#include <boost/corosio/ipv6_address.hpp>
#include <boost/corosio/local_datagram.hpp>
#include <boost/corosio/local_datagram_socket.hpp>
#include <boost/corosio/local_endpoint.hpp>
#include <boost/corosio/local_socket_pair.hpp>
#include <boost/corosio/local_stream.hpp>
#include <boost/corosio/local_stream_acceptor.hpp>
#include <boost/corosio/local_stream_socket.hpp>
#include <boost/corosio/random_access_file.hpp>
#include <boost/corosio/resolver.hpp>
#include <boost/corosio/resolver_results.hpp>
#include <boost/corosio/signal_set.hpp>
#include <boost/corosio/socket_option.hpp>
#include <boost/corosio/stream_file.hpp>
#include <boost/corosio/tcp.hpp>
#include <boost/corosio/tcp_acceptor.hpp>
#include <boost/corosio/tcp_server.hpp>
#include <boost/corosio/tcp_socket.hpp>
#include <boost/corosio/timer.hpp>
#include <boost/corosio/udp.hpp>
#include <boost/corosio/udp_socket.hpp>

#include <boost/corosio/tls_context.hpp>
#include <boost/corosio/openssl_stream.hpp>
Expand Down
Loading
Loading