include/boost/corosio/tcp_socket.hpp

90.5% Lines (38/42) 100.0% List of functions (23/23)
tcp_socket.hpp
f(x) Functions (23)
Function Calls Lines Blocks
boost::corosio::tcp_socket::connect_awaitable::connect_awaitable(boost::corosio::tcp_socket&, boost::corosio::endpoint) :192 8419x 100.0% 100.0% boost::corosio::tcp_socket::connect_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :195 8419x 100.0% 80.0% boost::corosio::tcp_socket::wait_awaitable::wait_awaitable(boost::corosio::tcp_socket&, boost::corosio::wait_type) :209 6x 100.0% 100.0% boost::corosio::tcp_socket::wait_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :212 6x 100.0% 80.0% boost::corosio::tcp_socket::tcp_socket(boost::corosio::tcp_socket&&) :257 188x 100.0% 100.0% boost::corosio::tcp_socket::operator=(boost::corosio::tcp_socket&&) :274 10x 100.0% 100.0% boost::corosio::tcp_socket::is_open() const :335 51379x 100.0% 100.0% boost::corosio::tcp_socket::connect(boost::corosio::endpoint) :379 8419x 100.0% 100.0% boost::corosio::tcp_socket::wait(boost::corosio::wait_type) :410 6x 100.0% 100.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::keep_alive>(boost::corosio::socket_option::keep_alive const&) :493 8x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::linger>(boost::corosio::socket_option::linger const&) :493 30x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::no_delay>(boost::corosio::socket_option::no_delay const&) :493 20x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::receive_buffer_size>(boost::corosio::socket_option::receive_buffer_size const&) :493 6x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::send_buffer_size>(boost::corosio::socket_option::send_buffer_size const&) :493 2x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::v6_only>(boost::corosio::socket_option::v6_only const&) :493 6x 71.4% 86.0% boost::corosio::socket_option::keep_alive boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::keep_alive>() const :520 8x 80.0% 88.0% boost::corosio::socket_option::linger boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::linger>() const :520 10x 80.0% 88.0% boost::corosio::socket_option::no_delay boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::no_delay>() const :520 22x 80.0% 88.0% boost::corosio::socket_option::receive_buffer_size boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::receive_buffer_size>() const :520 10x 80.0% 88.0% boost::corosio::socket_option::send_buffer_size boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::send_buffer_size>() const :520 6x 80.0% 88.0% boost::corosio::socket_option::v6_only boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::v6_only>() const :520 6x 80.0% 88.0% boost::corosio::tcp_socket::tcp_socket() :568 10x 100.0% 100.0% boost::corosio::tcp_socket::get() const :578 60014x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2026 Steve Gerbino
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #ifndef BOOST_COROSIO_TCP_SOCKET_HPP
12 #define BOOST_COROSIO_TCP_SOCKET_HPP
13
14 #include <boost/corosio/detail/config.hpp>
15 #include <boost/corosio/detail/platform.hpp>
16 #include <boost/corosio/detail/except.hpp>
17 #include <boost/corosio/detail/native_handle.hpp>
18 #include <boost/corosio/detail/op_base.hpp>
19 #include <boost/corosio/io/io_stream.hpp>
20 #include <boost/capy/io_result.hpp>
21 #include <boost/corosio/detail/buffer_param.hpp>
22 #include <boost/corosio/endpoint.hpp>
23 #include <boost/corosio/shutdown_type.hpp>
24 #include <boost/corosio/tcp.hpp>
25 #include <boost/corosio/wait_type.hpp>
26 #include <boost/capy/ex/executor_ref.hpp>
27 #include <boost/capy/ex/execution_context.hpp>
28 #include <boost/capy/ex/io_env.hpp>
29 #include <boost/capy/concept/executor.hpp>
30
31 #include <system_error>
32
33 #include <concepts>
34 #include <coroutine>
35 #include <cstddef>
36 #include <stop_token>
37 #include <type_traits>
38
39 namespace boost::corosio {
40
41 /** An asynchronous TCP socket for coroutine I/O.
42
43 This class provides asynchronous TCP socket operations that return
44 awaitable types. Each operation participates in the affine awaitable
45 protocol, ensuring coroutines resume on the correct executor.
46
47 The socket must be opened before performing I/O operations. Operations
48 support cancellation through `std::stop_token` via the affine protocol,
49 or explicitly through the `cancel()` member function.
50
51 @par Thread Safety
52 Distinct objects: Safe.@n
53 Shared objects: Unsafe. A socket must not have concurrent operations
54 of the same type (e.g., two simultaneous reads). One read and one
55 write may be in flight simultaneously.
56
57 @par Semantics
58 Wraps the platform TCP/IP stack. Operations dispatch to
59 OS socket APIs via the io_context reactor (epoll, IOCP,
60 kqueue). Satisfies @ref capy::Stream.
61
62 @par Example
63 @code
64 io_context ioc;
65 tcp_socket s(ioc);
66 s.open();
67
68 // Using structured bindings
69 auto [ec] = co_await s.connect(
70 endpoint(ipv4_address::loopback(), 8080));
71 if (ec)
72 co_return;
73
74 char buf[1024];
75 auto [read_ec, n] = co_await s.read_some(
76 capy::mutable_buffer(buf, sizeof(buf)));
77 @endcode
78 */
79 class BOOST_COROSIO_DECL tcp_socket : public io_stream
80 {
81 public:
82 /// The endpoint type used by this socket.
83 using endpoint_type = corosio::endpoint;
84
85 using shutdown_type = corosio::shutdown_type;
86 using enum corosio::shutdown_type;
87
88 /** Define backend hooks for TCP socket operations.
89
90 Platform backends (epoll, IOCP, kqueue, select) derive from
91 this to implement socket I/O, connection, and option management.
92 */
93 struct implementation : io_stream::implementation
94 {
95 /** Initiate an asynchronous connect to the given endpoint.
96
97 @param h Coroutine handle to resume on completion.
98 @param ex Executor for dispatching the completion.
99 @param ep The remote endpoint to connect to.
100 @param token Stop token for cancellation.
101 @param ec Output error code.
102
103 @return Coroutine handle to resume immediately.
104 */
105 virtual std::coroutine_handle<> connect(
106 std::coroutine_handle<> h,
107 capy::executor_ref ex,
108 endpoint ep,
109 std::stop_token token,
110 std::error_code* ec) = 0;
111
112 /** Initiate an asynchronous wait for socket readiness.
113
114 Completes when the socket becomes ready for the
115 specified direction, or an error condition is
116 reported. No bytes are transferred.
117
118 @param h Coroutine handle to resume on completion.
119 @param ex Executor for dispatching the completion.
120 @param w The direction to wait on.
121 @param token Stop token for cancellation.
122 @param ec Output error code.
123
124 @return Coroutine handle to resume immediately.
125 */
126 virtual std::coroutine_handle<> wait(
127 std::coroutine_handle<> h,
128 capy::executor_ref ex,
129 wait_type w,
130 std::stop_token token,
131 std::error_code* ec) = 0;
132
133 /** Shut down the socket for the given direction(s).
134
135 @param what The shutdown direction.
136
137 @return Error code on failure, empty on success.
138 */
139 virtual std::error_code shutdown(shutdown_type what) noexcept = 0;
140
141 /// Return the platform socket descriptor.
142 virtual native_handle_type native_handle() const noexcept = 0;
143
144 /** Request cancellation of pending asynchronous operations.
145
146 All outstanding operations complete with operation_canceled error.
147 Check `ec == cond::canceled` for portable comparison.
148 */
149 virtual void cancel() noexcept = 0;
150
151 /** Set a socket option.
152
153 @param level The protocol level (e.g. `SOL_SOCKET`).
154 @param optname The option name (e.g. `SO_KEEPALIVE`).
155 @param data Pointer to the option value.
156 @param size Size of the option value in bytes.
157 @return Error code on failure, empty on success.
158 */
159 virtual std::error_code set_option(
160 int level,
161 int optname,
162 void const* data,
163 std::size_t size) noexcept = 0;
164
165 /** Get a socket option.
166
167 @param level The protocol level (e.g. `SOL_SOCKET`).
168 @param optname The option name (e.g. `SO_KEEPALIVE`).
169 @param data Pointer to receive the option value.
170 @param size On entry, the size of the buffer. On exit,
171 the size of the option value.
172 @return Error code on failure, empty on success.
173 */
174 virtual std::error_code
175 get_option(int level, int optname, void* data, std::size_t* size)
176 const noexcept = 0;
177
178 /// Return the cached local endpoint.
179 virtual endpoint local_endpoint() const noexcept = 0;
180
181 /// Return the cached remote endpoint.
182 virtual endpoint remote_endpoint() const noexcept = 0;
183 };
184
185 /// Represent the awaitable returned by @ref connect.
186 struct connect_awaitable
187 : detail::void_op_base<connect_awaitable>
188 {
189 tcp_socket& s_;
190 endpoint endpoint_;
191
192 8419x connect_awaitable(tcp_socket& s, endpoint ep) noexcept
193 8419x : s_(s), endpoint_(ep) {}
194
195 8419x std::coroutine_handle<> dispatch(
196 std::coroutine_handle<> h, capy::executor_ref ex) const
197 {
198 8419x return s_.get().connect(h, ex, endpoint_, token_, &ec_);
199 }
200 };
201
202 /// Represent the awaitable returned by @ref wait.
203 struct wait_awaitable
204 : detail::void_op_base<wait_awaitable>
205 {
206 tcp_socket& s_;
207 wait_type w_;
208
209 6x wait_awaitable(tcp_socket& s, wait_type w) noexcept
210 6x : s_(s), w_(w) {}
211
212 6x std::coroutine_handle<> dispatch(
213 std::coroutine_handle<> h, capy::executor_ref ex) const
214 {
215 6x return s_.get().wait(h, ex, w_, token_, &ec_);
216 }
217 };
218
219 public:
220 /** Destructor.
221
222 Closes the socket if open, cancelling any pending operations.
223 */
224 ~tcp_socket() override;
225
226 /** Construct a socket from an execution context.
227
228 @param ctx The execution context that will own this socket.
229 */
230 explicit tcp_socket(capy::execution_context& ctx);
231
232 /** Construct a socket from an executor.
233
234 The socket is associated with the executor's context.
235
236 @param ex The executor whose context will own the socket.
237 */
238 template<class Ex>
239 requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_socket>) &&
240 capy::Executor<Ex>
241 explicit tcp_socket(Ex const& ex) : tcp_socket(ex.context())
242 {
243 }
244
245 /** Move constructor.
246
247 Transfers ownership of the socket resources.
248
249 @param other The socket to move from.
250
251 @pre No awaitables returned by @p other's methods exist.
252 @pre @p other is not referenced as a peer in any outstanding
253 accept awaitable.
254 @pre The execution context associated with @p other must
255 outlive this socket.
256 */
257 188x tcp_socket(tcp_socket&& other) noexcept : io_object(std::move(other)) {}
258
259 /** Move assignment operator.
260
261 Closes any existing socket and transfers ownership.
262
263 @param other The socket to move from.
264
265 @pre No awaitables returned by either `*this` or @p other's
266 methods exist.
267 @pre Neither `*this` nor @p other is referenced as a peer in
268 any outstanding accept awaitable.
269 @pre The execution context associated with @p other must
270 outlive this socket.
271
272 @return Reference to this socket.
273 */
274 10x tcp_socket& operator=(tcp_socket&& other) noexcept
275 {
276 10x if (this != &other)
277 {
278 10x close();
279 10x h_ = std::move(other.h_);
280 }
281 10x return *this;
282 }
283
284 tcp_socket(tcp_socket const&) = delete;
285 tcp_socket& operator=(tcp_socket const&) = delete;
286
287 /** Open the socket.
288
289 Creates a TCP socket and associates it with the platform
290 reactor (IOCP on Windows). Calling @ref connect on a closed
291 socket opens it automatically with the endpoint's address family,
292 so explicit `open()` is only needed when socket options must be
293 set before connecting.
294
295 @param proto The protocol (IPv4 or IPv6). Defaults to
296 `tcp::v4()`.
297
298 @throws std::system_error on failure.
299 */
300 void open(tcp proto = tcp::v4());
301
302 /** Bind the socket to a local endpoint.
303
304 Associates the socket with a local address and port before
305 connecting. Useful for multi-homed hosts or source-port
306 pinning.
307
308 @param ep The local endpoint to bind to.
309
310 @return An error code indicating success or the reason for
311 failure.
312
313 @par Error Conditions
314 @li `errc::address_in_use`: The endpoint is already in use.
315 @li `errc::address_not_available`: The address is not
316 available on any local interface.
317 @li `errc::permission_denied`: Insufficient privileges to
318 bind to the endpoint (e.g., privileged port).
319
320 @throws std::logic_error if the socket is not open.
321 */
322 [[nodiscard]] std::error_code bind(endpoint ep);
323
324 /** Close the socket.
325
326 Releases socket resources. Any pending operations complete
327 with `errc::operation_canceled`.
328 */
329 void close();
330
331 /** Check if the socket is open.
332
333 @return `true` if the socket is open and ready for operations.
334 */
335 51379x bool is_open() const noexcept
336 {
337 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
338 return h_ && get().native_handle() != ~native_handle_type(0);
339 #else
340 51379x return h_ && get().native_handle() >= 0;
341 #endif
342 }
343
344 /** Initiate an asynchronous connect operation.
345
346 If the socket is not already open, it is opened automatically
347 using the address family of @p ep (IPv4 or IPv6). If the socket
348 is already open, the existing file descriptor is used as-is.
349
350 The operation supports cancellation via `std::stop_token` through
351 the affine awaitable protocol. If the associated stop token is
352 triggered, the operation completes immediately with
353 `errc::operation_canceled`.
354
355 @param ep The remote endpoint to connect to.
356
357 @return An awaitable that completes with `io_result<>`.
358 Returns success (default error_code) on successful connection,
359 or an error code on failure including:
360 - connection_refused: No server listening at endpoint
361 - timed_out: Connection attempt timed out
362 - network_unreachable: No route to host
363 - operation_canceled: Cancelled via stop_token or cancel().
364 Check `ec == cond::canceled` for portable comparison.
365
366 @throws std::system_error if the socket needs to be opened
367 and the open fails.
368
369 @par Preconditions
370 This socket must outlive the returned awaitable.
371
372 @par Example
373 @code
374 // Socket opened automatically with correct address family:
375 auto [ec] = co_await s.connect(endpoint);
376 if (ec) { ... }
377 @endcode
378 */
379 8419x auto connect(endpoint ep)
380 {
381 8419x if (!is_open())
382 40x open(ep.is_v6() ? tcp::v6() : tcp::v4());
383 8419x return connect_awaitable(*this, ep);
384 }
385
386 /** Wait for the socket to become ready in a given direction.
387
388 Suspends until the socket is ready for the requested
389 direction, or an error condition is reported. No bytes
390 are transferred — useful for integrating with C libraries
391 that own the I/O on a nonblocking fd and only need
392 readiness notification (e.g. libpq async, libssh).
393
394 The operation supports cancellation via `std::stop_token`
395 through the affine awaitable protocol. If the associated
396 stop token is triggered, the operation completes
397 immediately with `errc::operation_canceled`.
398
399 @param w The wait direction (read, write, or error).
400
401 @return An awaitable that completes with `io_result<>`.
402 On success, no bytes have been consumed from the
403 stream; a subsequent `read_some` (for read waits)
404 returns the available data.
405
406 @par Preconditions
407 The socket must be open. This socket must outlive the
408 returned awaitable.
409 */
410 6x [[nodiscard]] auto wait(wait_type w)
411 {
412 6x return wait_awaitable(*this, w);
413 }
414
415 /** Cancel any pending asynchronous operations.
416
417 All outstanding operations complete with `errc::operation_canceled`.
418 Check `ec == cond::canceled` for portable comparison.
419 */
420 void cancel();
421
422 /** Get the native socket handle.
423
424 Returns the underlying platform-specific socket descriptor.
425 On POSIX systems this is an `int` file descriptor.
426 On Windows this is a `SOCKET` handle.
427
428 @return The native socket handle, or -1/INVALID_SOCKET if not open.
429
430 @par Preconditions
431 None. May be called on closed sockets.
432 */
433 native_handle_type native_handle() const noexcept;
434
435 /** Disable sends or receives on the socket.
436
437 TCP connections are full-duplex: each direction (send and receive)
438 operates independently. This function allows you to close one or
439 both directions without destroying the socket.
440
441 @li @ref shutdown_send sends a TCP FIN packet to the peer,
442 signaling that you have no more data to send. You can still
443 receive data until the peer also closes their send direction.
444 This is the most common use case, typically called before
445 close() to ensure graceful connection termination.
446
447 @li @ref shutdown_receive disables reading on the socket. This
448 does NOT send anything to the peer - they are not informed
449 and may continue sending data. Subsequent reads will fail
450 or return end-of-file. Incoming data may be discarded or
451 buffered depending on the operating system.
452
453 @li @ref shutdown_both combines both effects: sends a FIN and
454 disables reading.
455
456 When the peer shuts down their send direction (sends a FIN),
457 subsequent read operations will complete with `capy::cond::eof`.
458 Use the portable condition test rather than comparing error
459 codes directly:
460
461 @code
462 auto [ec, n] = co_await sock.read_some(buffer);
463 if (ec == capy::cond::eof)
464 {
465 // Peer closed their send direction
466 }
467 @endcode
468
469 Any error from the underlying system call is silently discarded
470 because it is unlikely to be helpful.
471
472 @param what Determines what operations will no longer be allowed.
473 */
474 void shutdown(shutdown_type what);
475
476 /** Set a socket option.
477
478 Applies a type-safe socket option to the underlying socket.
479 The option type encodes the protocol level and option name.
480
481 @par Example
482 @code
483 sock.set_option( socket_option::no_delay( true ) );
484 sock.set_option( socket_option::receive_buffer_size( 65536 ) );
485 @endcode
486
487 @param opt The option to set.
488
489 @throws std::logic_error if the socket is not open.
490 @throws std::system_error on failure.
491 */
492 template<class Option>
493 72x void set_option(Option const& opt)
494 {
495 72x if (!is_open())
496 detail::throw_logic_error("set_option: socket not open");
497 72x std::error_code ec = get().set_option(
498 Option::level(), Option::name(), opt.data(), opt.size());
499 72x if (ec)
500 detail::throw_system_error(ec, "tcp_socket::set_option");
501 72x }
502
503 /** Get a socket option.
504
505 Retrieves the current value of a type-safe socket option.
506
507 @par Example
508 @code
509 auto nd = sock.get_option<socket_option::no_delay>();
510 if ( nd.value() )
511 // Nagle's algorithm is disabled
512 @endcode
513
514 @return The current option value.
515
516 @throws std::logic_error if the socket is not open.
517 @throws std::system_error on failure.
518 */
519 template<class Option>
520 62x Option get_option() const
521 {
522 62x if (!is_open())
523 detail::throw_logic_error("get_option: socket not open");
524 62x Option opt{};
525 62x std::size_t sz = opt.size();
526 std::error_code ec =
527 62x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
528 62x if (ec)
529 detail::throw_system_error(ec, "tcp_socket::get_option");
530 62x opt.resize(sz);
531 62x return opt;
532 }
533
534 /** Get the local endpoint of the socket.
535
536 Returns the local address and port to which the socket is bound.
537 For a connected socket, this is the local side of the connection.
538 The endpoint is cached when the connection is established.
539
540 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
541 the socket is not connected.
542
543 @par Thread Safety
544 The cached endpoint value is set during connect/accept completion
545 and cleared during close(). This function may be called concurrently
546 with I/O operations, but must not be called concurrently with
547 connect(), accept(), or close().
548 */
549 endpoint local_endpoint() const noexcept;
550
551 /** Get the remote endpoint of the socket.
552
553 Returns the remote address and port to which the socket is connected.
554 The endpoint is cached when the connection is established.
555
556 @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
557 the socket is not connected.
558
559 @par Thread Safety
560 The cached endpoint value is set during connect/accept completion
561 and cleared during close(). This function may be called concurrently
562 with I/O operations, but must not be called concurrently with
563 connect(), accept(), or close().
564 */
565 endpoint remote_endpoint() const noexcept;
566
567 protected:
568 10x tcp_socket() noexcept = default;
569
570 explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
571
572 private:
573 friend class tcp_acceptor;
574
575 /// Open the socket for the given protocol triple.
576 void open_for_family(int family, int type, int protocol);
577
578 60014x inline implementation& get() const noexcept
579 {
580 60014x return *static_cast<implementation*>(h_.get());
581 }
582 };
583
584 } // namespace boost::corosio
585
586 #endif
587