include/boost/corosio/tcp_socket.hpp

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