cheatah
Source

stdlib/socket/socket.hpp

1// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).
2// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.
3#pragma once
5/**
6 * @file socket.hpp
7 * @brief cheatah `socket` — a small wrapper around BSD/POSIX TCP sockets,
8 * in the spirit of Python's `socket`. `import socket` to use it.
9 *
10 * The recommended API is the owning `socket::Conn` / `socket::Listener` guards (from
11 * `socket.open(host, port)` / `socket.serve(host, port, backlog)`), which close their fd at
12 * scope exit. A **flat, file-descriptor-based** API is also available for hand-built servers:
13 * pass the integer fd from `socket()` / `tcp_listen()` / `accept()` to the other calls (an
14 * unclosed fd there is a resource leak — prefer the guards). IPv4 + TCP only; host names are
15 * resolved with `getaddrinfo` (so `"localhost"`, `"127.0.0.1"`, and DNS names all work).
16 * Errors are a negative return (or empty string for `recv`); `last_error()` gives the `errno` text.
17 *
18 * `import socket` includes this header AND links `libcheatah_socket`. Unit tests:
19 * `stdlib/tests/socket_test.cpp`; the suite runs under AddressSanitizer (the
20 * `asan` preset) and Valgrind (`security/run-valgrind.sh`) on every QA-gate run.
21 *
22 * @note Every call is a thin wrapper over one or two syscalls. Only `recv` and
23 * `last_error` allocate (their returned `std::string`, plus `recv`'s reused
24 * per-thread scratch buffer); the fd/status calls return a `long long` and do
25 * not allocate (the resolver's transient `getaddrinfo` list is freed in-call).
26 */
27#include <string>
29namespace cheatah::socket {
31// ---- high-level convenience (what a server/client usually wants) ----
33/**
34 * Create a TCP socket bound to @p host:@p port and put it in the listening state (sets
35 * `SO_REUSEADDR`).
36 *
37 * Performs socket()/set_reuseaddr/bind/listen in one shot; on any failure it closes the
38 * partially-created socket and returns -1, so the caller never leaks an fd. On success the
39 * returned fd is owned by the caller and must be passed to close() when done.
40 * @param host interface to bind ("127.0.0.1", "0.0.0.0", …).
41 * @param port TCP port (0 = let the OS pick — read it back with local_port()).
42 * @param backlog pending-connection queue length.
43 * @return the listening fd, or -1 on error.
44 * @complexity O(1) + resolution (a few syscalls).
45 * @alloc none (the resolver's transient `getaddrinfo` list is freed before returning).
46 * @test CheatahSocket.Loopback
47 * @crtest SocketCompileRun.TcpListen
48 * @systest StdlibE2E.Socket
49 */
50long long tcp_listen(const std::string& host, long long port, long long backlog);
52/**
53 * Create a TCP socket and connect it to @p host:@p port.
54 *
55 * The host is resolved via `getaddrinfo`, so names, "localhost", and dotted IPs all work; the
56 * connect blocks until the handshake completes or fails. On failure the socket is closed and
57 * -1 is returned; on success the caller owns the connected fd and must close() it.
58 * @param host destination host (name or IP).
59 * @param port destination port.
60 * @return the connected fd, or -1 on error.
61 * @complexity O(1) + DNS resolution.
62 * @alloc none (the resolver's transient `getaddrinfo` list is freed before returning).
63 * @concurrency blocks until the TCP handshake completes or fails.
64 * @test CheatahSocket.Loopback
65 * @crtest SocketCompileRun.TcpConnect
66 * @systest StdlibE2E.Socket
67 */
68long long tcp_connect(const std::string& host, long long port);
70// ---- per-connection I/O ----
72/**
73 * Accept one pending connection.
74 *
75 * Blocks until a client connects, then returns a new fd for that one connection (the listening
76 * fd stays open for further accepts). The returned client fd is owned by the caller and must be
77 * closed separately; the peer address is discarded.
78 * @param fd a listening fd.
79 * @return the connected client fd, or -1 on error.
80 * @complexity O(1) syscall (blocks until a client arrives).
81 * @alloc none.
82 * @concurrency blocks the calling thread until a client connects.
83 * @test CheatahSocket.Loopback
84 * @crtest SocketCompileRun.Accept
85 * @systest StdlibE2E.Socket
86 */
87long long accept(long long fd);
89/**
90 * Receive up to @p bufsize bytes.
91 *
92 * Blocks for one `recv` and returns whatever bytes arrive (possibly fewer than @p bufsize); the
93 * result is binary-safe, so a returned string may contain embedded NULs and its length is the
94 * true byte count. A clean EOF (peer closed) and an error both yield "", so check last_error()
95 * to tell them apart; @p bufsize <= 0 also returns "" without touching the socket.
96 * @param fd a connected fd.
97 * @param bufsize maximum bytes to read.
98 * @return the bytes read (binary-safe), or "" on EOF/error.
99 * @complexity O(@p bufsize).
100 * @alloc allocates the returned string (and grows a reused per-thread scratch buffer
101 * up to @p bufsize on first use).
102 * @concurrency blocks until data, EOF, or the set_timeout() deadline; a shutdown() from
103 * another thread wakes it with EOF.
104 * @test CheatahSocket.Loopback
105 * @crtest SocketCompileRun.Recv
106 * @systest StdlibE2E.Socket
107 */
108std::string recv(long long fd, long long bufsize);
110/**
111 * Send some of @p data (one `send`).
112 *
113 * Issues a single `send`, which may transmit fewer bytes than supplied (a partial send); the
114 * caller is responsible for re-sending the remainder, or use sendall() to loop automatically.
115 * @param fd a connected fd.
116 * @param data bytes to send.
117 * @return bytes actually sent, or -1 on error.
118 * @complexity O(n).
119 * @alloc none (`MSG_NOSIGNAL`, so a broken pipe never raises `SIGPIPE`).
120 * @test CheatahSocket.Sendall
121 * @crtest SocketCompileRun.Send
122 * @systest StdlibE2E.Socket
123 */
124long long send(long long fd, const std::string& data);
126/**
127 * Send @p data in full, looping until all bytes are written.
128 *
129 * Repeatedly calls `send` on the unsent remainder until every byte is written, so unlike send()
130 * there are no partial sends to handle; it aborts with -1 the moment a `send` returns <= 0
131 * (error or peer hang-up), in which case some bytes may already have been transmitted.
132 * @param fd a connected fd.
133 * @param data bytes to send.
134 * @return 0 on success, -1 on error.
135 * @complexity O(n).
136 * @alloc none.
137 * @concurrency may block while the peer's receive window is full; bounded per `send`
138 * by the set_timeout() send deadline.
139 * @test CheatahSocket.Sendall
140 * @crtest SocketCompileRun.Sendall
141 * @systest StdlibE2E.Socket
142 */
143long long sendall(long long fd, const std::string& data);
145/**
146 * Close a socket.
147 *
148 * Releases the fd back to the OS; after this the fd is invalid and must not be reused. Closing an
149 * already-closed or never-opened fd fails with -1 (EBADF), which is how the BadFd test exercises
150 * the error path.
151 * @param fd the fd to close.
152 * @return 0 on success, -1 on error.
153 * @complexity O(1) syscall.
154 * @alloc none.
155 * @test CheatahSocket.BadFd
156 * @crtest SocketCompileRun.Close
157 * @systest StdlibE2E.Socket
158 */
159long long close(long long fd);
161/**
162 * Half-close @p fd in both directions (::shutdown SHUT_RDWR) WITHOUT releasing it.
163 * A blocking recv() on another thread returns immediately (EOF) — the safe way to
164 * wake a reader for a clean shutdown. The fd is still owned by the caller and must
165 * be close()d afterwards.
166 * @param fd the fd to half-close.
167 * @return 0 on success, -1 on error.
168 * @complexity O(1) syscall.
169 * @alloc none.
170 * @concurrency safe to call from another thread while a recv() on @p fd blocks — waking
171 * that reader is exactly what it is for.
172 * @test CheatahSocket.TimeoutThenShutdown
173 */
174long long shutdown(long long fd);
176// ---- low-level BSD primitives (for clients/servers built by hand) ----
178/**
179 * Create an IPv4 TCP socket.
180 *
181 * Allocates an unbound, unconnected AF_INET/SOCK_STREAM fd; you must follow up with bind()+listen()
182 * or connect() before it can carry data, and close() it when done.
183 * @return the new fd, or -1 on error.
184 * @complexity O(1).
185 * @alloc none.
186 * @test CheatahSocket.ListenLowLevel
187 * @crtest SocketCompileRun.Socket
188 * @systest StdlibE2E.Socket
189 */
190long long socket();
192/**
193 * Enable `SO_REUSEADDR` on @p fd.
194 *
195 * Lets a subsequent bind() reuse a local address still lingering in TIME_WAIT, so a restarted
196 * server can re-listen on the same port immediately; call it before bind().
197 * @warning `SO_REUSEADDR` trades TIME_WAIT protection for restartability: by skipping the
198 * kernel's cooldown, delayed segments from a previous connection on the same
199 * address can in principle reach the new socket.
200 * @param fd the socket.
201 * @return 0 on success, -1 on error.
202 * @complexity O(1).
203 * @alloc none.
204 * @test CheatahSocket.ListenLowLevel
205 * @crtest SocketCompileRun.SetReuseaddr
206 * @systest StdlibE2E.Socket
207 */
208long long set_reuseaddr(long long fd);
210/**
211 * Bind @p fd to @p host:@p port.
212 *
213 * Resolves @p host via `getaddrinfo` and assigns the resulting local address to the socket; a
214 * resolution failure returns -1 with errno set to EADDRNOTAVAIL (see the ResolveFailure test).
215 * @param fd the socket.
216 * @param host interface to bind.
217 * @param port TCP port (0 = OS-assigned).
218 * @return 0 on success, -1 on error.
219 * @complexity O(1) + resolution.
220 * @alloc none (the resolver's transient `getaddrinfo` list is freed before returning).
221 * @test CheatahSocket.ListenLowLevel, CheatahSocket.ResolveFailure
222 * @crtest SocketCompileRun.Bind
223 * @systest StdlibE2E.Socket
224 */
225long long bind(long long fd, const std::string& host, long long port);
227/**
228 * Mark @p fd as a passive (listening) socket.
229 *
230 * Switches an already-bound socket into the listening state so accept() can pull connections from
231 * it; @p backlog caps how many fully-established connections may queue before new ones are refused.
232 * @param fd the socket.
233 * @param backlog queue length.
234 * @return 0 on success, -1 on error.
235 * @complexity O(1).
236 * @alloc none.
237 * @test CheatahSocket.ListenLowLevel
238 * @crtest SocketCompileRun.Listen
239 * @systest StdlibE2E.Socket
240 */
241long long listen(long long fd, long long backlog);
243/**
244 * Connect @p fd to @p host:@p port.
245 *
246 * Resolves @p host and blocks until the TCP handshake succeeds or fails; a refused connection
247 * returns -1 with errno ECONNREFUSED (see the ConnectRefused test). Unlike tcp_connect() it does
248 * not close the fd on failure — the caller still owns @p fd.
249 * @param fd the socket.
250 * @param host destination.
251 * @param port destination port.
252 * @return 0 on success, -1 on error.
253 * @complexity O(1) + DNS.
254 * @alloc none (the resolver's transient `getaddrinfo` list is freed before returning).
255 * @concurrency blocks until the TCP handshake completes or fails.
256 * @test CheatahSocket.ConnectRefused
257 * @crtest SocketCompileRun.Connect
258 * @systest StdlibE2E.Socket
259 */
260long long connect(long long fd, const std::string& host, long long port);
262/**
263 * The local TCP port @p fd is bound to (useful after binding to port 0).
264 *
265 * Reads the address actually assigned via `getsockname` and returns its port in host byte order;
266 * this is the way to discover the ephemeral port the OS chose when you bound to port 0.
267 * @param fd a bound socket.
268 * @return the port, or -1 on error.
269 * @complexity O(1) syscall.
270 * @alloc none.
271 * @test CheatahSocket.Loopback
272 * @crtest SocketCompileRun.LocalPort
273 * @systest StdlibE2E.Socket
274 */
275long long local_port(long long fd);
277/**
278 * Bound both blocking directions of @p fd by @p timeout_ms (SO_RCVTIMEO + SO_SNDTIMEO), so a
279 * silent peer cannot hang a recv/send forever. A recv that times out returns "" (check
280 * last_error() to distinguish from EOF). @p timeout_ms <= 0 clears the timeouts (block forever).
281 *
282 * @param fd a socket.
283 * @param timeout_ms the per-operation bound in milliseconds.
284 * @return 0 on success, -1 on error.
285 * @complexity O(1) (two setsockopt calls).
286 * @alloc none.
287 * @test CheatahSocket.TimeoutThenShutdown
288 */
289long long set_timeout(long long fd, long long timeout_ms);
291/**
292 * @brief A UDP socket — the DATAGRAM face beside the stream one. Unbound until @ref bind; a
293 * client that only sends needs no bind (the kernel picks a port on the first @ref sendto).
294 *
295 * What a datagram is for: data whose NEXT packet supersedes this one — a presence pose, a live
296 * drag, a simulation snapshot. It may be dropped, duplicated or reordered by the network and the
297 * receiver must be written for that (newest-wins by stamp); nothing here acks, retries or orders.
298 * Everything that must arrive rides TCP (@ref tcp_connect).
299 *
300 * @return the socket fd, or -1 (@ref last_error says why).
301 * @complexity O(1).
302 * @alloc none.
303 * @test CheatahSocket.UdpLoopback
304 * @crtest SocketCompileRun.UdpLoopback
305 * @systest StdlibE2E.Socket
306 */
307long long udp_socket();
309/**
310 * @brief Send one datagram to @p host:@p port (an IPv4 literal or a name; resolved per call, so
311 * cache the address yourself on a hot path). The whole of @p data is one packet: keep it
312 * under the path MTU (1200 bytes is safe on the public internet, 65507 the absolute cap).
313 * @param fd a socket from @ref udp_socket.
314 * @param host destination host.
315 * @param port destination port.
316 * @param data the packet.
317 * @return bytes sent (== data.size()), or -1.
318 * @complexity O(len) + the kernel's copy.
319 * @alloc none (the resolve is on the stack).
320 * @test CheatahSocket.UdpLoopback
321 * @crtest SocketCompileRun.UdpLoopback
322 * @systest StdlibE2E.Socket
323 */
324long long sendto(long long fd, const std::string& host, long long port, const std::string& data);
326/**
327 * @brief Receive one datagram: at most @p bufsize bytes of it (the rest of a larger packet is
328 * DISCARDED — the datagram contract), with the sender's address in @p out_host / @p out_port.
329 * Blocks until a packet arrives, or until @ref set_timeout's window passes (an empty string,
330 * @p out_port 0).
331 * @param fd a socket from @ref udp_socket, bound with @ref bind.
332 * @param bufsize the most bytes to take.
333 * @param out_host the sender's address, dotted (empty on timeout/error).
334 * @param out_port the sender's port (0 on timeout/error).
335 * @return the packet's bytes, or empty.
336 * @complexity O(bufsize) worst case.
337 * @alloc the returned string (the receive buffer is a reused per-thread scratch).
338 * @test CheatahSocket.UdpLoopback
339 * @crtest SocketCompileRun.UdpLoopback
340 * @systest StdlibE2E.Socket
341 */
342std::string recvfrom(long long fd, long long bufsize, std::string& out_host, long long& out_port);
344/**
345 * The message for the current `errno`.
346 *
347 * Returns the human-readable text for the thread's current `errno`; call it right after a function
348 * reports failure (a -1 return, or "" from recv), since any later syscall may overwrite `errno`.
349 * @return `strerror(errno)`.
350 * @complexity O(1).
351 * @alloc allocates the returned string.
352 * @test CheatahSocket.ConnectRefused
353 * @crtest SocketCompileRun.LastError
354 * @systest StdlibE2E.Socket
355 */
356std::string last_error();
358// ---- owning RAII connections (the `with`-friendly, leak-proof API) ----
360/**
361 * @brief An owning TCP connection — a socket fd whose destructor closes it.
362 *
363 * The RAII counterpart to the fd-based calls above, and the C++/cheatah analog of a
364 * Python socket used in a `with` block. A `Conn` owns exactly one fd; when it is
365 * destroyed (scope exit, including a `return`/`break`/exception out of a `with` body)
366 * or explicitly close()d, the fd is released — so a connection opened with
367 * `with socket.open(host, port) as c { … }` cannot leak. Move-only: copying would give
368 * two owners of one fd and double-close it, so the copy operations are deleted and a
369 * moved-from `Conn` is left closed.
370 */
371class Conn {
372public:
373 /**
374 * Construct a closed connection (owns no fd).
375 * @complexity O(1).
376 * @alloc none.
377 * @test CheatahSocket.ConnDefaultIsClosed
378 */
379 Conn() = default;
380 /**
381 * Adopt an already-connected fd (e.g. from tcp_connect()/accept()); the `Conn` now owns it.
382 * @param fd a connected fd to take ownership of (-1 for a closed connection).
383 * @complexity O(1).
384 * @alloc none.
385 * @test CheatahSocket.ConnGuardClosesOnScopeExit
386 */
387 explicit Conn(long long fd) : fd_(fd) {}
388 Conn(const Conn&) = delete;
389 Conn& operator=(const Conn&) = delete;
390 /**
391 * Move-construct, taking over @p other's fd (the moved-from `Conn` becomes closed).
392 * @param other the connection to move from.
393 * @complexity O(1).
394 * @alloc none.
395 * @test CheatahSocket.ConnMoveTransfersOwnership
396 */
397 Conn(Conn&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
398 /**
399 * Move-assign, closing this fd first, then taking over @p other's (which becomes closed).
400 * @param other the connection to move from.
401 * @return reference to this connection.
402 * @complexity O(1).
403 * @alloc none.
404 * @test CheatahSocket.ConnMoveTransfersOwnership
405 */
406 Conn& operator=(Conn&& other) noexcept;
407 /**
408 * Close the fd if still open.
409 * @complexity O(1) syscall.
410 * @alloc none.
411 * @test CheatahSocket.ConnGuardClosesOnScopeExit
412 */
413 ~Conn();
415 /**
416 * Is a connection open?
417 * @return true iff this owns an open fd.
418 * @complexity O(1).
419 * @alloc none.
420 * @test CheatahSocket.ConnDefaultIsClosed
421 */
422 bool is_open() const { return fd_ >= 0; }
423 /**
424 * The raw fd, for the low-level calls or to hand to tls.open(conn.fd(), …).
425 * @return the owned fd, or -1 when closed.
426 * @complexity O(1).
427 * @alloc none.
428 * @test CheatahSocket.ConnGuardClosesOnScopeExit
429 */
430 long long fd() const { return fd_; }
431 /**
432 * Send some of @p data (one send(); see the free send()).
433 * @param data bytes to send.
434 * @return bytes actually sent, or -1 on error.
435 * @complexity O(n).
436 * @alloc none.
437 * @test CheatahSocket.ConnLoopback
438 */
439 long long send(const std::string& data) const;
440 /**
441 * Send @p data in full, looping until every byte is written (see the free sendall()).
442 * @param data bytes to send.
443 * @return 0 on success, -1 on error.
444 * @complexity O(n).
445 * @alloc none.
446 * @test CheatahSocket.ConnLoopback
447 */
448 long long sendall(const std::string& data) const;
449 /**
450 * Receive up to @p bufsize bytes (see the free recv()).
451 * @param bufsize maximum bytes to read.
452 * @return the bytes read (binary-safe), or "" on EOF/error.
453 * @complexity O(@p bufsize).
454 * @alloc allocates the returned string (plus the free recv()'s reused per-thread
455 * scratch buffer on growth).
456 * @concurrency blocks until data, EOF, or the set_timeout() deadline.
457 * @test CheatahSocket.ConnLoopback
458 */
459 std::string recv(long long bufsize) const;
460 /**
461 * Bound both blocking directions by @p timeout_ms (see the free set_timeout()).
462 * @param timeout_ms per-operation bound in milliseconds (<= 0 clears it).
463 * @return 0 on success, -1 on error.
464 * @complexity O(1).
465 * @alloc none.
466 * @test CheatahSocket.ConnLoopback
467 */
468 long long set_timeout(long long timeout_ms) const;
469 /**
470 * The local TCP port this fd is bound to (see the free local_port()).
471 * @return the port, or -1 on error.
472 * @complexity O(1) syscall.
473 * @alloc none.
474 * @test CheatahSocket.ConnLoopback
475 */
476 long long local_port() const;
477 /**
478 * Half-close both directions WITHOUT releasing the fd (see the free shutdown()) — wakes a
479 * blocked reader for a clean shutdown; still call close() (or let the destructor) afterward.
480 * @return 0 on success, -1 on error.
481 * @complexity O(1) syscall.
482 * @alloc none.
483 * @test CheatahSocket.ConnLoopback
484 */
485 long long shutdown() const;
486 /**
487 * Close the fd now (idempotent — the destructor will not close it again).
488 * @return 0 on success, -1 if already closed or on error.
489 * @complexity O(1) syscall.
490 * @alloc none.
491 * @test CheatahSocket.ConnGuardClosesOnScopeExit
492 */
493 long long close();
495private:
496 long long fd_ = -1;
497};
499/**
500 * @brief An owning listening socket; accept() yields owning Conn clients; the destructor closes it.
501 *
502 * The server-side RAII guard: `with socket.serve(host, port, backlog) as server { … }` keeps the
503 * listening fd for the block and closes it on exit. Each accept() returns an owning Conn, so a
504 * whole server loop leaks neither the listener nor its clients. Move-only, like Conn.
505 */
506class Listener {
507public:
508 /**
509 * Construct a closed listener (owns no fd).
510 * @complexity O(1).
511 * @alloc none.
512 * @test CheatahSocket.ListenerDefaultIsClosed
513 */
514 Listener() = default;
515 /**
516 * Adopt an already-listening fd (e.g. from tcp_listen()); the `Listener` now owns it.
517 * @param fd a listening fd to take ownership of (-1 for a closed listener).
518 * @complexity O(1).
519 * @alloc none.
520 * @test CheatahSocket.ListenerLoopback
521 */
522 explicit Listener(long long fd) : fd_(fd) {}
523 Listener(const Listener&) = delete;
524 Listener& operator=(const Listener&) = delete;
525 /**
526 * Move-construct, taking over @p other's fd (the moved-from `Listener` becomes closed).
527 * @param other the listener to move from.
528 * @complexity O(1).
529 * @alloc none.
530 * @test CheatahSocket.ListenerLoopback
531 */
532 Listener(Listener&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
533 /**
534 * Move-assign, closing this fd first, then taking over @p other's (which becomes closed).
535 * @param other the listener to move from.
536 * @return reference to this listener.
537 * @complexity O(1).
538 * @alloc none.
539 * @test CheatahSocket.ListenerLoopback
540 */
541 Listener& operator=(Listener&& other) noexcept;
542 /**
543 * Close the listening fd if still open.
544 * @complexity O(1) syscall.
545 * @alloc none.
546 * @test CheatahSocket.ListenerLoopback
547 */
548 ~Listener();
550 /**
551 * Is the listener open?
552 * @return true iff this owns an open listening fd.
553 * @complexity O(1).
554 * @alloc none.
555 * @test CheatahSocket.ListenerDefaultIsClosed
556 */
557 bool is_open() const { return fd_ >= 0; }
558 /**
559 * The raw listening fd.
560 * @return the owned fd, or -1 when closed.
561 * @complexity O(1).
562 * @alloc none.
563 * @test CheatahSocket.ListenerLoopback
564 */
565 long long fd() const { return fd_; }
566 /**
567 * Accept one pending connection, returned as an owning Conn (the listener stays open).
568 * @return an owning Conn for the client (its is_open() is false on error).
569 * @complexity O(1) syscall (blocks until a client arrives).
570 * @alloc none.
571 * @concurrency blocks the calling thread until a client connects.
572 * @test CheatahSocket.ConnLoopback
573 */
574 Conn accept() const;
575 /**
576 * The local TCP port this listener is bound to (useful after binding to port 0).
577 * @return the port, or -1 on error.
578 * @complexity O(1) syscall.
579 * @alloc none.
580 * @test CheatahSocket.ListenerLoopback
581 */
582 long long local_port() const;
583 /**
584 * Close the listening fd now (idempotent — the destructor will not close it again).
585 * @return 0 on success, -1 if already closed or on error.
586 * @complexity O(1) syscall.
587 * @alloc none.
588 * @test CheatahSocket.ListenerLoopback
589 */
590 long long close();
592private:
593 long long fd_ = -1;
594};
596/**
597 * Open a client TCP connection to @p host:@p port and return it as an owning Conn (the
598 * RAII, `with`-friendly form of tcp_connect()).
599 * @param host destination host (name or IP).
600 * @param port destination port.
601 * @return an owning Conn; on failure its is_open() is false (see last_error()).
602 * @complexity O(1) + DNS resolution.
603 * @alloc none beyond the Conn itself.
604 * @concurrency blocks until the TCP handshake completes or fails.
605 * @test CheatahSocket.ConnLoopback
606 */
607Conn open(const std::string& host, long long port);
609/**
610 * Create a listening server socket bound to @p host:@p port and return it as an owning
611 * Listener (the RAII, `with`-friendly form of tcp_listen()).
612 * @param host interface to bind ("127.0.0.1", "0.0.0.0", …).
613 * @param port TCP port (0 = let the OS pick — read it back with Listener::local_port()).
614 * @param backlog pending-connection queue length.
615 * @return an owning Listener; on failure its is_open() is false (see last_error()).
616 * @complexity O(1) + resolution (a few syscalls).
617 * @alloc none beyond the Listener itself.
618 * @test CheatahSocket.ListenerLoopback
619 */
620Listener serve(const std::string& host, long long port, long long backlog);
622} // namespace cheatah::socket