Skip to content

Commit b968d3c

Browse files
committed
Add Unix domain socket support and collapse reactor backends
Add local_stream_socket, local_stream_acceptor, local_datagram_socket, local_endpoint, and local_socket_pair for Unix domain socket I/O. The public API mirrors TCP/UDP with protocol-appropriate differences (path-based endpoints, socketpair, release_socket for fd passing). Replace 42 per-backend reactor files (14 each for epoll, kqueue, select) with parameterized templates instantiated from per-backend traits structs. Each backend provides a traits type capturing its platform-specific socket creation, write policy, and accept policy. The reactor_types<Traits> template in reactor_backend.hpp generates all concrete socket, service, acceptor, and op types. Virtual method overrides move into the CRTP bases, and select's notify_reactor() moves into register_op() via a compile-time trait, eliminating all select-specific method overrides. New files: - {epoll,kqueue,select}_traits.hpp: per-backend policies - reactor_{stream,datagram}_ops.hpp: parameterized op types - reactor_socket_finals.hpp: final socket/acceptor types - reactor_service_finals.hpp: per-protocol service types - reactor_backend.hpp: accept impl and reactor_types bundle
1 parent 17fa5e5 commit b968d3c

File tree

97 files changed

+8987
-4708
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+8987
-4708
lines changed

doc/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
** xref:4.guide/4m.error-handling.adoc[Error Handling]
3737
** xref:4.guide/4n.buffers.adoc[Buffer Sequences]
3838
** xref:4.guide/4o.file-io.adoc[File I/O]
39+
** xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets]
3940
* xref:5.testing/5.intro.adoc[Testing]
4041
** xref:5.testing/5a.mocket.adoc[Mock Sockets]
4142
* xref:benchmark-report.adoc[Benchmarks]

doc/modules/ROOT/pages/4.guide/4a.tcp-networking.adoc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,9 @@ UDP includes a checksum covering the header and data. The receiver verifies
473473
the checksum and discards corrupted packets. Unlike TCP, UDP doesn't
474474
retransmit—the data is simply lost.
475475

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

478480
== Ports and Sockets
479481

@@ -699,10 +701,14 @@ listening socket.
699701

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

702-
* **socket** — Connect to servers, send and receive data
703-
* **tcp_acceptor** — Listen for and accept incoming connections
704+
* **tcp_socket** — Connect to servers, send and receive data over TCP
705+
* **udp_socket** — Send and receive datagrams over UDP
706+
* **tcp_acceptor** — Listen for and accept incoming TCP connections
707+
* **local_stream_socket** — Stream-oriented Unix domain sockets for local IPC
708+
* **local_datagram_socket** — Datagram-oriented Unix domain sockets for local IPC
704709
* **resolver** — Translate hostnames to IP addresses
705-
* **endpoint** — Represent addresses and ports
710+
* **endpoint** — Represent IP addresses and ports
711+
* **local_endpoint** — Represent Unix socket paths
706712

707713
All operations are asynchronous and return awaitables. You don't manage raw
708714
socket handles or deal with platform-specific APIs directly.
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
//
2+
// Copyright (c) 2026 Michael Vandeberg
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/corosio
8+
//
9+
10+
= Unix Domain Sockets
11+
12+
Unix domain sockets provide inter-process communication (IPC) on the same
13+
machine without going through the TCP/IP network stack. They use filesystem
14+
paths instead of IP addresses and ports, offering lower latency and higher
15+
throughput than loopback TCP.
16+
17+
[NOTE]
18+
====
19+
Code snippets assume:
20+
[source,cpp]
21+
----
22+
#include <boost/corosio/local_stream_socket.hpp>
23+
#include <boost/corosio/local_stream_acceptor.hpp>
24+
#include <boost/corosio/local_datagram_socket.hpp>
25+
#include <boost/corosio/local_socket_pair.hpp>
26+
#include <boost/corosio/local_endpoint.hpp>
27+
#include <boost/capy/buffers.hpp>
28+
29+
namespace corosio = boost::corosio;
30+
namespace capy = boost::capy;
31+
----
32+
====
33+
34+
== When to Use Unix Sockets
35+
36+
Use Unix domain sockets instead of TCP when:
37+
38+
* Both endpoints are on the same machine
39+
* You need lower latency (no TCP/IP stack overhead)
40+
* You need higher throughput for local communication
41+
* You want filesystem-based access control (file permissions on the socket path)
42+
43+
Common use cases include database connections (PostgreSQL, MySQL, Redis),
44+
container networking, and microservice communication on a single host.
45+
46+
== Socket Types
47+
48+
Corosio provides two Unix socket types, mirroring the TCP/UDP split:
49+
50+
[cols="1,1,2"]
51+
|===
52+
| Class | Protocol | Description
53+
54+
| `local_stream_socket`
55+
| `SOCK_STREAM`
56+
| Reliable, ordered byte stream (like TCP). Supports connect/accept.
57+
58+
| `local_datagram_socket`
59+
| `SOCK_DGRAM`
60+
| Message-oriented datagrams (like UDP). Preserves message boundaries.
61+
|===
62+
63+
== Stream Sockets
64+
65+
Stream sockets work like TCP: a server binds and listens on a path, clients
66+
connect, and both sides read and write byte streams.
67+
68+
=== Server (Acceptor)
69+
70+
[source,cpp]
71+
----
72+
capy::task<> server(corosio::io_context& ioc)
73+
{
74+
corosio::local_stream_acceptor acc(ioc);
75+
acc.open();
76+
77+
auto ec = acc.bind(corosio::local_endpoint("/tmp/my_app.sock"));
78+
if (ec) co_return;
79+
80+
ec = acc.listen();
81+
if (ec) co_return;
82+
83+
corosio::local_stream_socket peer(ioc);
84+
auto [accept_ec] = co_await acc.accept(peer);
85+
if (accept_ec) co_return;
86+
87+
// peer is now connected — read and write as with tcp_socket
88+
char buf[1024];
89+
auto [read_ec, n] = co_await peer.read_some(
90+
capy::mutable_buffer(buf, sizeof(buf)));
91+
}
92+
----
93+
94+
The acceptor does **not** automatically remove the socket file on close.
95+
You must `unlink()` the path before binding (if it exists) and after you
96+
are done:
97+
98+
[source,cpp]
99+
----
100+
::unlink("/tmp/my_app.sock"); // remove stale socket
101+
acc.bind(corosio::local_endpoint("/tmp/my_app.sock"));
102+
----
103+
104+
=== Client
105+
106+
[source,cpp]
107+
----
108+
capy::task<> client(corosio::io_context& ioc)
109+
{
110+
corosio::local_stream_socket s(ioc);
111+
112+
// connect() opens the socket automatically
113+
auto [ec] = co_await s.connect(
114+
corosio::local_endpoint("/tmp/my_app.sock"));
115+
if (ec) co_return;
116+
117+
char const msg[] = "hello";
118+
auto [wec, n] = co_await s.write_some(
119+
capy::const_buffer(msg, sizeof(msg)));
120+
}
121+
----
122+
123+
=== Socket Pairs
124+
125+
For bidirectional IPC between a parent and child (or two coroutines),
126+
use `make_local_stream_pair()` which calls the `socketpair()` system call:
127+
128+
[source,cpp]
129+
----
130+
auto [s1, s2] = corosio::make_local_stream_pair(ioc);
131+
132+
// Data written to s1 can be read from s2, and vice versa.
133+
co_await s1.write_some(capy::const_buffer("ping", 4));
134+
135+
char buf[16];
136+
auto [ec, n] = co_await s2.read_some(
137+
capy::mutable_buffer(buf, sizeof(buf)));
138+
// buf contains "ping"
139+
----
140+
141+
This is the fastest way to create a connected pair — it uses a single
142+
`socketpair()` syscall with no filesystem paths involved.
143+
144+
== Datagram Sockets
145+
146+
Datagram sockets preserve message boundaries. Each `send` delivers exactly
147+
one message that the receiver gets as a complete unit from `recv`.
148+
149+
=== Connectionless Mode
150+
151+
Both sides bind to paths, then use `send_to`/`recv_from`:
152+
153+
[source,cpp]
154+
----
155+
corosio::local_datagram_socket s(ioc);
156+
s.open();
157+
s.bind(corosio::local_endpoint("/tmp/my_dgram.sock"));
158+
159+
// Send to a specific peer
160+
co_await s.send_to(
161+
capy::const_buffer("hello", 5),
162+
corosio::local_endpoint("/tmp/peer.sock"));
163+
164+
// Receive from any sender
165+
corosio::local_endpoint sender;
166+
auto [ec, n] = co_await s.recv_from(
167+
capy::mutable_buffer(buf, sizeof(buf)), sender);
168+
----
169+
170+
=== Connected Mode
171+
172+
After calling `connect()`, use `send`/`recv` without specifying the peer:
173+
174+
[source,cpp]
175+
----
176+
auto [s1, s2] = corosio::make_local_datagram_pair(ioc);
177+
178+
co_await s1.send(capy::const_buffer("msg", 3));
179+
180+
auto [ec, n] = co_await s2.recv(
181+
capy::mutable_buffer(buf, sizeof(buf)));
182+
----
183+
184+
== Local Endpoints
185+
186+
Unix socket endpoints use filesystem paths instead of IP+port:
187+
188+
[source,cpp]
189+
----
190+
// Create from a path
191+
corosio::local_endpoint ep("/tmp/my_app.sock");
192+
193+
// Query the path
194+
std::string_view path = ep.path();
195+
196+
// Check if empty (unbound)
197+
bool bound = !ep.empty();
198+
----
199+
200+
The maximum path length is 107 bytes (the `sun_path` field in `sockaddr_un`
201+
minus the null terminator). Paths longer than this throw
202+
`std::errc::filename_too_long`.
203+
204+
=== Abstract Sockets (Linux Only)
205+
206+
On Linux, paths starting with a null byte (`'\0'`) create abstract sockets
207+
that exist in a kernel namespace rather than the filesystem. They don't leave
208+
socket files behind and don't need cleanup:
209+
210+
[source,cpp]
211+
----
212+
// Abstract socket — no file created
213+
corosio::local_endpoint ep(std::string_view("\0/my_app", 8));
214+
assert(ep.is_abstract());
215+
----
216+
217+
== Comparison with TCP
218+
219+
[cols="1,1,1"]
220+
|===
221+
| Feature | TCP (`tcp_socket`) | Unix (`local_stream_socket`)
222+
223+
| Addressing
224+
| IP address + port
225+
| Filesystem path
226+
227+
| Scope
228+
| Network (any machine)
229+
| Local machine only
230+
231+
| Latency
232+
| Higher (TCP/IP stack)
233+
| Lower (kernel shortcut)
234+
235+
| Throughput
236+
| Limited by network stack
237+
| Higher for local IPC
238+
239+
| Access control
240+
| Firewall rules
241+
| File permissions
242+
243+
| DNS resolution
244+
| Yes (via `resolver`)
245+
| No (direct paths)
246+
247+
| Platform
248+
| All platforms
249+
| POSIX only (Linux, macOS, BSD)
250+
|===
251+
252+
== Platform Support
253+
254+
Unix domain sockets are available on all POSIX platforms:
255+
256+
* **Linux** — Full support including abstract sockets
257+
* **macOS** — Full support (no abstract sockets)
258+
* **FreeBSD** — Full support (no abstract sockets)
259+
260+
Windows has limited AF_UNIX support (since Windows 10 1803) but Corosio
261+
does not currently support Unix sockets on Windows.
262+
263+
== Next Steps
264+
265+
* xref:4d.sockets.adoc[TCP Sockets] — TCP socket operations
266+
* xref:4e.tcp-acceptor.adoc[TCP Acceptors] — TCP listener operations
267+
* xref:4f.endpoints.adoc[IP Endpoints] — IP address and port endpoints

doc/modules/ROOT/pages/glossary.adoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ Lazy::
148148
A coroutine or operation that doesn't start until explicitly triggered.
149149
`capy::task` is lazy—it starts when awaited.
150150

151+
Local Endpoint::
152+
A filesystem path used as the address for a Unix domain socket. Represented
153+
by `corosio::local_endpoint`. See xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets].
154+
151155
== M
152156

153157
Mocket::
@@ -254,6 +258,14 @@ Type Erasure::
254258
Hiding concrete types behind an abstract interface. Enables runtime
255259
polymorphism without templates.
256260

261+
== U
262+
263+
Unix Domain Socket::
264+
A socket that communicates between processes on the same machine using
265+
filesystem paths instead of IP addresses and ports. Available as stream
266+
(`local_stream_socket`) and datagram (`local_datagram_socket`) variants.
267+
See xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets].
268+
257269
== W
258270

259271
Wait::

doc/modules/ROOT/pages/index.adoc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ the _IoAwaitable_ protocol, ensuring your coroutines resume on the correct
2020
executor without manual dispatch.
2121

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

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

@@ -154,3 +157,4 @@ int main()
154157
* xref:4.guide/4b.concurrent-programming.adoc[Concurrent Programming] — Coroutines and strands
155158
* xref:4.guide/4c.io-context.adoc[I/O Context] — Understand the event loop
156159
* xref:4.guide/4d.sockets.adoc[Sockets] — Learn socket operations in detail
160+
* xref:4.guide/4p.unix-sockets.adoc[Unix Domain Sockets] — Local IPC with stream and datagram sockets

include/boost/corosio.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,26 @@
1717
#include <boost/corosio/io_context.hpp>
1818
#include <boost/corosio/ipv4_address.hpp>
1919
#include <boost/corosio/ipv6_address.hpp>
20+
#include <boost/corosio/local_datagram.hpp>
21+
#include <boost/corosio/local_datagram_socket.hpp>
22+
#include <boost/corosio/local_endpoint.hpp>
23+
#include <boost/corosio/local_socket_pair.hpp>
24+
#include <boost/corosio/local_stream.hpp>
25+
#include <boost/corosio/local_stream_acceptor.hpp>
26+
#include <boost/corosio/local_stream_socket.hpp>
2027
#include <boost/corosio/random_access_file.hpp>
2128
#include <boost/corosio/resolver.hpp>
2229
#include <boost/corosio/resolver_results.hpp>
2330
#include <boost/corosio/signal_set.hpp>
2431
#include <boost/corosio/socket_option.hpp>
2532
#include <boost/corosio/stream_file.hpp>
33+
#include <boost/corosio/tcp.hpp>
2634
#include <boost/corosio/tcp_acceptor.hpp>
2735
#include <boost/corosio/tcp_server.hpp>
2836
#include <boost/corosio/tcp_socket.hpp>
2937
#include <boost/corosio/timer.hpp>
38+
#include <boost/corosio/udp.hpp>
39+
#include <boost/corosio/udp_socket.hpp>
3040

3141
#include <boost/corosio/tls_context.hpp>
3242
#include <boost/corosio/openssl_stream.hpp>

0 commit comments

Comments
 (0)