include/boost/corosio/tcp_socket.hpp

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