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 8514x 100.0% 100.0% boost::corosio::tcp_socket::connect_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :170 8514x 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 51961x 100.0% 100.0% boost::corosio::tcp_socket::connect(boost::corosio::endpoint) :337 8514x 100.0% 100.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::keep_alive>(boost::corosio::socket_option::keep_alive const&) :422 8x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::linger>(boost::corosio::socket_option::linger const&) :422 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&) :422 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&) :422 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&) :422 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&) :422 6x 71.4% 86.0% boost::corosio::socket_option::keep_alive boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::keep_alive>() const :449 8x 80.0% 88.0% boost::corosio::socket_option::linger boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::linger>() const :449 10x 80.0% 88.0% boost::corosio::socket_option::no_delay boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::no_delay>() const :449 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 :449 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 :449 6x 80.0% 88.0% boost::corosio::socket_option::v6_only boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::v6_only>() const :449 6x 80.0% 88.0% boost::corosio::tcp_socket::tcp_socket() :497 10x 100.0% 100.0% boost::corosio::tcp_socket::get() const :507 60677x 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 8514x connect_awaitable(tcp_socket& s, endpoint ep) noexcept
168 8514x : s_(s), endpoint_(ep) {}
169
170 8514x std::coroutine_handle<> dispatch(
171 std::coroutine_handle<> h, capy::executor_ref ex) const
172 {
173 8514x 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 51961x 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 51961x 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 8514x auto connect(endpoint ep)
338 {
339 8514x if (!is_open())
340 16x open(ep.is_v6() ? tcp::v6() : tcp::v4());
341 8514x 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.
400
401 @param what Determines what operations will no longer be allowed.
402 */
403 void shutdown(shutdown_type what);
404
405 /** Set a socket option.
406
407 Applies a type-safe socket option to the underlying socket.
408 The option type encodes the protocol level and option name.
409
410 @par Example
411 @code
412 sock.set_option( socket_option::no_delay( true ) );
413 sock.set_option( socket_option::receive_buffer_size( 65536 ) );
414 @endcode
415
416 @param opt The option to set.
417
418 @throws std::logic_error if the socket is not open.
419 @throws std::system_error on failure.
420 */
421 template<class Option>
422 60x void set_option(Option const& opt)
423 {
424 60x if (!is_open())
425 detail::throw_logic_error("set_option: socket not open");
426 60x std::error_code ec = get().set_option(
427 Option::level(), Option::name(), opt.data(), opt.size());
428 60x if (ec)
429 detail::throw_system_error(ec, "tcp_socket::set_option");
430 60x }
431
432 /** Get a socket option.
433
434 Retrieves the current value of a type-safe socket option.
435
436 @par Example
437 @code
438 auto nd = sock.get_option<socket_option::no_delay>();
439 if ( nd.value() )
440 // Nagle's algorithm is disabled
441 @endcode
442
443 @return The current option value.
444
445 @throws std::logic_error if the socket is not open.
446 @throws std::system_error on failure.
447 */
448 template<class Option>
449 62x Option get_option() const
450 {
451 62x if (!is_open())
452 detail::throw_logic_error("get_option: socket not open");
453 62x Option opt{};
454 62x std::size_t sz = opt.size();
455 std::error_code ec =
456 62x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
457 62x if (ec)
458 detail::throw_system_error(ec, "tcp_socket::get_option");
459 62x opt.resize(sz);
460 62x return opt;
461 }
462
463 /** Get the local endpoint of the socket.
464
465 Returns the local address and port to which the socket is bound.
466 For a connected socket, this is the local side of the connection.
467 The endpoint is cached when the connection is established.
468
469 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
470 the socket is not connected.
471
472 @par Thread Safety
473 The cached endpoint value is set during connect/accept completion
474 and cleared during close(). This function may be called concurrently
475 with I/O operations, but must not be called concurrently with
476 connect(), accept(), or close().
477 */
478 endpoint local_endpoint() const noexcept;
479
480 /** Get the remote endpoint of the socket.
481
482 Returns the remote address and port to which the socket is connected.
483 The endpoint is cached when the connection is established.
484
485 @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
486 the socket is not connected.
487
488 @par Thread Safety
489 The cached endpoint value is set during connect/accept completion
490 and cleared during close(). This function may be called concurrently
491 with I/O operations, but must not be called concurrently with
492 connect(), accept(), or close().
493 */
494 endpoint remote_endpoint() const noexcept;
495
496 protected:
497 10x tcp_socket() noexcept = default;
498
499 explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
500
501 private:
502 friend class tcp_acceptor;
503
504 /// Open the socket for the given protocol triple.
505 void open_for_family(int family, int type, int protocol);
506
507 60677x inline implementation& get() const noexcept
508 {
509 60677x return *static_cast<implementation*>(h_.get());
510 }
511 };
512
513 } // namespace boost::corosio
514
515 #endif
516