include/boost/corosio/tcp_socket.hpp

80.8% Lines (105/130) 100.0% List of functions (22/22)
tcp_socket.hpp
f(x) Functions (22)
Function Calls Lines Blocks
boost::corosio::tcp_socket::connect_awaitable::connect_awaitable(boost::corosio::tcp_socket&, boost::corosio::endpoint) :171 7981x 100.0% 100.0% boost::corosio::tcp_socket::connect_awaitable::await_ready() const :177 7981x 100.0% 100.0% boost::corosio::tcp_socket::connect_awaitable::await_resume() const :182 7981x 75.0% 67.0% boost::corosio::tcp_socket::connect_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :189 7981x 100.0% 82.0% boost::corosio::tcp_socket::tcp_socket(boost::corosio::tcp_socket&&) :235 2294x 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 :313 54520x 100.0% 100.0% boost::corosio::tcp_socket::connect(boost::corosio::endpoint) :357 7981x 100.0% 100.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::keep_alive>(boost::corosio::socket_option::keep_alive const&) :442 8x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::linger>(boost::corosio::socket_option::linger const&) :442 2056x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::no_delay>(boost::corosio::socket_option::no_delay const&) :442 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&) :442 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&) :442 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&) :442 6x 71.4% 86.0% boost::corosio::socket_option::keep_alive boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::keep_alive>() const :469 8x 80.0% 88.0% boost::corosio::socket_option::linger boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::linger>() const :469 10x 80.0% 88.0% boost::corosio::socket_option::no_delay boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::no_delay>() const :469 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 :469 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 :469 6x 80.0% 88.0% boost::corosio::socket_option::v6_only boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::v6_only>() const :469 6x 80.0% 88.0% boost::corosio::tcp_socket::tcp_socket() :517 10x 100.0% 100.0% boost::corosio::tcp_socket::get() const :527 62613x 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 7981x connect_awaitable(tcp_socket& s, endpoint ep) noexcept
172 7981x : s_(s)
173 7981x , endpoint_(ep)
174 {
175 7981x }
176
177 7981x bool await_ready() const noexcept
178 {
179 7981x return token_.stop_requested();
180 }
181
182 7981x capy::io_result<> await_resume() const noexcept
183 {
184 7981x if (token_.stop_requested())
185 return {make_error_code(std::errc::operation_canceled)};
186 7981x return {ec_};
187 }
188
189 7981x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
190 -> std::coroutine_handle<>
191 {
192 7981x token_ = env->stop_token;
193 7981x 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 2294x 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 /** Bind the socket to a local endpoint.
281
282 Associates the socket with a local address and port before
283 connecting. Useful for multi-homed hosts or source-port
284 pinning.
285
286 @param ep The local endpoint to bind to.
287
288 @return An error code indicating success or the reason for
289 failure.
290
291 @par Error Conditions
292 @li `errc::address_in_use`: The endpoint is already in use.
293 @li `errc::address_not_available`: The address is not
294 available on any local interface.
295 @li `errc::permission_denied`: Insufficient privileges to
296 bind to the endpoint (e.g., privileged port).
297
298 @throws std::logic_error if the socket is not open.
299 */
300 [[nodiscard]] std::error_code bind(endpoint ep);
301
302 /** Close the socket.
303
304 Releases socket resources. Any pending operations complete
305 with `errc::operation_canceled`.
306 */
307 void close();
308
309 /** Check if the socket is open.
310
311 @return `true` if the socket is open and ready for operations.
312 */
313 54520x bool is_open() const noexcept
314 {
315 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
316 return h_ && get().native_handle() != ~native_handle_type(0);
317 #else
318 54520x return h_ && get().native_handle() >= 0;
319 #endif
320 }
321
322 /** Initiate an asynchronous connect operation.
323
324 If the socket is not already open, it is opened automatically
325 using the address family of @p ep (IPv4 or IPv6). If the socket
326 is already open, the existing file descriptor is used as-is.
327
328 The operation supports cancellation via `std::stop_token` through
329 the affine awaitable protocol. If the associated stop token is
330 triggered, the operation completes immediately with
331 `errc::operation_canceled`.
332
333 @param ep The remote endpoint to connect to.
334
335 @return An awaitable that completes with `io_result<>`.
336 Returns success (default error_code) on successful connection,
337 or an error code on failure including:
338 - connection_refused: No server listening at endpoint
339 - timed_out: Connection attempt timed out
340 - network_unreachable: No route to host
341 - operation_canceled: Cancelled via stop_token or cancel().
342 Check `ec == cond::canceled` for portable comparison.
343
344 @throws std::system_error if the socket needs to be opened
345 and the open fails.
346
347 @par Preconditions
348 This socket must outlive the returned awaitable.
349
350 @par Example
351 @code
352 // Socket opened automatically with correct address family:
353 auto [ec] = co_await s.connect(endpoint);
354 if (ec) { ... }
355 @endcode
356 */
357 7981x auto connect(endpoint ep)
358 {
359 7981x if (!is_open())
360 16x open(ep.is_v6() ? tcp::v6() : tcp::v4());
361 7981x return connect_awaitable(*this, ep);
362 }
363
364 /** Cancel any pending asynchronous operations.
365
366 All outstanding operations complete with `errc::operation_canceled`.
367 Check `ec == cond::canceled` for portable comparison.
368 */
369 void cancel();
370
371 /** Get the native socket handle.
372
373 Returns the underlying platform-specific socket descriptor.
374 On POSIX systems this is an `int` file descriptor.
375 On Windows this is a `SOCKET` handle.
376
377 @return The native socket handle, or -1/INVALID_SOCKET if not open.
378
379 @par Preconditions
380 None. May be called on closed sockets.
381 */
382 native_handle_type native_handle() const noexcept;
383
384 /** Disable sends or receives on the socket.
385
386 TCP connections are full-duplex: each direction (send and receive)
387 operates independently. This function allows you to close one or
388 both directions without destroying the socket.
389
390 @li @ref shutdown_send sends a TCP FIN packet to the peer,
391 signaling that you have no more data to send. You can still
392 receive data until the peer also closes their send direction.
393 This is the most common use case, typically called before
394 close() to ensure graceful connection termination.
395
396 @li @ref shutdown_receive disables reading on the socket. This
397 does NOT send anything to the peer - they are not informed
398 and may continue sending data. Subsequent reads will fail
399 or return end-of-file. Incoming data may be discarded or
400 buffered depending on the operating system.
401
402 @li @ref shutdown_both combines both effects: sends a FIN and
403 disables reading.
404
405 When the peer shuts down their send direction (sends a FIN),
406 subsequent read operations will complete with `capy::cond::eof`.
407 Use the portable condition test rather than comparing error
408 codes directly:
409
410 @code
411 auto [ec, n] = co_await sock.read_some(buffer);
412 if (ec == capy::cond::eof)
413 {
414 // Peer closed their send direction
415 }
416 @endcode
417
418 Any error from the underlying system call is silently discarded
419 because it is unlikely to be helpful.
420
421 @param what Determines what operations will no longer be allowed.
422 */
423 void shutdown(shutdown_type what);
424
425 /** Set a socket option.
426
427 Applies a type-safe socket option to the underlying socket.
428 The option type encodes the protocol level and option name.
429
430 @par Example
431 @code
432 sock.set_option( socket_option::no_delay( true ) );
433 sock.set_option( socket_option::receive_buffer_size( 65536 ) );
434 @endcode
435
436 @param opt The option to set.
437
438 @throws std::logic_error if the socket is not open.
439 @throws std::system_error on failure.
440 */
441 template<class Option>
442 2098x void set_option(Option const& opt)
443 {
444 2098x if (!is_open())
445 detail::throw_logic_error("set_option: socket not open");
446 2098x std::error_code ec = get().set_option(
447 Option::level(), Option::name(), opt.data(), opt.size());
448 2098x if (ec)
449 detail::throw_system_error(ec, "tcp_socket::set_option");
450 2098x }
451
452 /** Get a socket option.
453
454 Retrieves the current value of a type-safe socket option.
455
456 @par Example
457 @code
458 auto nd = sock.get_option<socket_option::no_delay>();
459 if ( nd.value() )
460 // Nagle's algorithm is disabled
461 @endcode
462
463 @return The current option value.
464
465 @throws std::logic_error if the socket is not open.
466 @throws std::system_error on failure.
467 */
468 template<class Option>
469 62x Option get_option() const
470 {
471 62x if (!is_open())
472 detail::throw_logic_error("get_option: socket not open");
473 62x Option opt{};
474 62x std::size_t sz = opt.size();
475 std::error_code ec =
476 62x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
477 62x if (ec)
478 detail::throw_system_error(ec, "tcp_socket::get_option");
479 62x opt.resize(sz);
480 62x return opt;
481 }
482
483 /** Get the local endpoint of the socket.
484
485 Returns the local address and port to which the socket is bound.
486 For a connected socket, this is the local side of the connection.
487 The endpoint is cached when the connection is established.
488
489 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
490 the socket is not connected.
491
492 @par Thread Safety
493 The cached endpoint value is set during connect/accept completion
494 and cleared during close(). This function may be called concurrently
495 with I/O operations, but must not be called concurrently with
496 connect(), accept(), or close().
497 */
498 endpoint local_endpoint() const noexcept;
499
500 /** Get the remote endpoint of the socket.
501
502 Returns the remote address and port to which the socket is connected.
503 The endpoint is cached when the connection is established.
504
505 @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
506 the socket is not connected.
507
508 @par Thread Safety
509 The cached endpoint value is set during connect/accept completion
510 and cleared during close(). This function may be called concurrently
511 with I/O operations, but must not be called concurrently with
512 connect(), accept(), or close().
513 */
514 endpoint remote_endpoint() const noexcept;
515
516 protected:
517 10x tcp_socket() noexcept = default;
518
519 explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
520
521 private:
522 friend class tcp_acceptor;
523
524 /// Open the socket for the given protocol triple.
525 void open_for_family(int family, int type, int protocol);
526
527 62613x inline implementation& get() const noexcept
528 {
529 62613x return *static_cast<implementation*>(h_.get());
530 }
531 };
532
533 } // namespace boost::corosio
534
535 #endif
536