include/boost/corosio/tcp_socket.hpp

88.9% Lines (32/36) 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) :170 5206x 100.0% 100.0% boost::corosio::tcp_socket::connect_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :173 5206x 100.0% 80.0% boost::corosio::tcp_socket::tcp_socket(boost::corosio::tcp_socket&&) :218 176x 100.0% 100.0% boost::corosio::tcp_socket::operator=(boost::corosio::tcp_socket&&) :235 10x 100.0% 100.0% boost::corosio::tcp_socket::is_open() const :296 32085x 100.0% 100.0% boost::corosio::tcp_socket::connect(boost::corosio::endpoint) :340 5206x 100.0% 100.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::keep_alive>(boost::corosio::socket_option::keep_alive const&) :425 8x 71.4% 86.0% void boost::corosio::tcp_socket::set_option<boost::corosio::socket_option::linger>(boost::corosio::socket_option::linger const&) :425 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&) :425 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&) :425 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&) :425 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&) :425 6x 71.4% 86.0% boost::corosio::socket_option::keep_alive boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::keep_alive>() const :452 8x 80.0% 88.0% boost::corosio::socket_option::linger boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::linger>() const :452 10x 80.0% 88.0% boost::corosio::socket_option::no_delay boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::no_delay>() const :452 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 :452 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 :452 6x 80.0% 88.0% boost::corosio::socket_option::v6_only boost::corosio::tcp_socket::get_option<boost::corosio::socket_option::v6_only>() const :452 6x 80.0% 88.0% boost::corosio::tcp_socket::tcp_socket() :500 10x 100.0% 100.0% boost::corosio::tcp_socket::get() const :510 37497x 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 /// The endpoint type used by this socket.
82 using endpoint_type = corosio::endpoint;
83
84 using shutdown_type = corosio::shutdown_type;
85 using enum corosio::shutdown_type;
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 : detail::void_op_base<connect_awaitable>
166 {
167 tcp_socket& s_;
168 endpoint endpoint_;
169
170 5206x connect_awaitable(tcp_socket& s, endpoint ep) noexcept
171 5206x : s_(s), endpoint_(ep) {}
172
173 5206x std::coroutine_handle<> dispatch(
174 std::coroutine_handle<> h, capy::executor_ref ex) const
175 {
176 5206x return s_.get().connect(h, ex, endpoint_, token_, &ec_);
177 }
178 };
179
180 public:
181 /** Destructor.
182
183 Closes the socket if open, cancelling any pending operations.
184 */
185 ~tcp_socket() override;
186
187 /** Construct a socket from an execution context.
188
189 @param ctx The execution context that will own this socket.
190 */
191 explicit tcp_socket(capy::execution_context& ctx);
192
193 /** Construct a socket from an executor.
194
195 The socket is associated with the executor's context.
196
197 @param ex The executor whose context will own the socket.
198 */
199 template<class Ex>
200 requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_socket>) &&
201 capy::Executor<Ex>
202 explicit tcp_socket(Ex const& ex) : tcp_socket(ex.context())
203 {
204 }
205
206 /** Move constructor.
207
208 Transfers ownership of the socket resources.
209
210 @param other The socket to move from.
211
212 @pre No awaitables returned by @p other's methods exist.
213 @pre @p other is not referenced as a peer in any outstanding
214 accept awaitable.
215 @pre The execution context associated with @p other must
216 outlive this socket.
217 */
218 176x tcp_socket(tcp_socket&& other) noexcept : io_object(std::move(other)) {}
219
220 /** Move assignment operator.
221
222 Closes any existing socket and transfers ownership.
223
224 @param other The socket to move from.
225
226 @pre No awaitables returned by either `*this` or @p other's
227 methods exist.
228 @pre Neither `*this` nor @p other is referenced as a peer in
229 any outstanding accept awaitable.
230 @pre The execution context associated with @p other must
231 outlive this socket.
232
233 @return Reference to this socket.
234 */
235 10x tcp_socket& operator=(tcp_socket&& other) noexcept
236 {
237 10x if (this != &other)
238 {
239 10x close();
240 10x h_ = std::move(other.h_);
241 }
242 10x return *this;
243 }
244
245 tcp_socket(tcp_socket const&) = delete;
246 tcp_socket& operator=(tcp_socket const&) = delete;
247
248 /** Open the socket.
249
250 Creates a TCP socket and associates it with the platform
251 reactor (IOCP on Windows). Calling @ref connect on a closed
252 socket opens it automatically with the endpoint's address family,
253 so explicit `open()` is only needed when socket options must be
254 set before connecting.
255
256 @param proto The protocol (IPv4 or IPv6). Defaults to
257 `tcp::v4()`.
258
259 @throws std::system_error on failure.
260 */
261 void open(tcp proto = tcp::v4());
262
263 /** Bind the socket to a local endpoint.
264
265 Associates the socket with a local address and port before
266 connecting. Useful for multi-homed hosts or source-port
267 pinning.
268
269 @param ep The local endpoint to bind to.
270
271 @return An error code indicating success or the reason for
272 failure.
273
274 @par Error Conditions
275 @li `errc::address_in_use`: The endpoint is already in use.
276 @li `errc::address_not_available`: The address is not
277 available on any local interface.
278 @li `errc::permission_denied`: Insufficient privileges to
279 bind to the endpoint (e.g., privileged port).
280
281 @throws std::logic_error if the socket is not open.
282 */
283 [[nodiscard]] std::error_code bind(endpoint ep);
284
285 /** Close the socket.
286
287 Releases socket resources. Any pending operations complete
288 with `errc::operation_canceled`.
289 */
290 void close();
291
292 /** Check if the socket is open.
293
294 @return `true` if the socket is open and ready for operations.
295 */
296 32085x bool is_open() const noexcept
297 {
298 #if BOOST_COROSIO_HAS_IOCP && !defined(BOOST_COROSIO_MRDOCS)
299 return h_ && get().native_handle() != ~native_handle_type(0);
300 #else
301 32085x return h_ && get().native_handle() >= 0;
302 #endif
303 }
304
305 /** Initiate an asynchronous connect operation.
306
307 If the socket is not already open, it is opened automatically
308 using the address family of @p ep (IPv4 or IPv6). If the socket
309 is already open, the existing file descriptor is used as-is.
310
311 The operation supports cancellation via `std::stop_token` through
312 the affine awaitable protocol. If the associated stop token is
313 triggered, the operation completes immediately with
314 `errc::operation_canceled`.
315
316 @param ep The remote endpoint to connect to.
317
318 @return An awaitable that completes with `io_result<>`.
319 Returns success (default error_code) on successful connection,
320 or an error code on failure including:
321 - connection_refused: No server listening at endpoint
322 - timed_out: Connection attempt timed out
323 - network_unreachable: No route to host
324 - operation_canceled: Cancelled via stop_token or cancel().
325 Check `ec == cond::canceled` for portable comparison.
326
327 @throws std::system_error if the socket needs to be opened
328 and the open fails.
329
330 @par Preconditions
331 This socket must outlive the returned awaitable.
332
333 @par Example
334 @code
335 // Socket opened automatically with correct address family:
336 auto [ec] = co_await s.connect(endpoint);
337 if (ec) { ... }
338 @endcode
339 */
340 5206x auto connect(endpoint ep)
341 {
342 5206x if (!is_open())
343 38x open(ep.is_v6() ? tcp::v6() : tcp::v4());
344 5206x return connect_awaitable(*this, ep);
345 }
346
347 /** Cancel any pending asynchronous operations.
348
349 All outstanding operations complete with `errc::operation_canceled`.
350 Check `ec == cond::canceled` for portable comparison.
351 */
352 void cancel();
353
354 /** Get the native socket handle.
355
356 Returns the underlying platform-specific socket descriptor.
357 On POSIX systems this is an `int` file descriptor.
358 On Windows this is a `SOCKET` handle.
359
360 @return The native socket handle, or -1/INVALID_SOCKET if not open.
361
362 @par Preconditions
363 None. May be called on closed sockets.
364 */
365 native_handle_type native_handle() const noexcept;
366
367 /** Disable sends or receives on the socket.
368
369 TCP connections are full-duplex: each direction (send and receive)
370 operates independently. This function allows you to close one or
371 both directions without destroying the socket.
372
373 @li @ref shutdown_send sends a TCP FIN packet to the peer,
374 signaling that you have no more data to send. You can still
375 receive data until the peer also closes their send direction.
376 This is the most common use case, typically called before
377 close() to ensure graceful connection termination.
378
379 @li @ref shutdown_receive disables reading on the socket. This
380 does NOT send anything to the peer - they are not informed
381 and may continue sending data. Subsequent reads will fail
382 or return end-of-file. Incoming data may be discarded or
383 buffered depending on the operating system.
384
385 @li @ref shutdown_both combines both effects: sends a FIN and
386 disables reading.
387
388 When the peer shuts down their send direction (sends a FIN),
389 subsequent read operations will complete with `capy::cond::eof`.
390 Use the portable condition test rather than comparing error
391 codes directly:
392
393 @code
394 auto [ec, n] = co_await sock.read_some(buffer);
395 if (ec == capy::cond::eof)
396 {
397 // Peer closed their send direction
398 }
399 @endcode
400
401 Any error from the underlying system call is silently discarded
402 because it is unlikely to be helpful.
403
404 @param what Determines what operations will no longer be allowed.
405 */
406 void shutdown(shutdown_type what);
407
408 /** Set a socket option.
409
410 Applies a type-safe socket option to the underlying socket.
411 The option type encodes the protocol level and option name.
412
413 @par Example
414 @code
415 sock.set_option( socket_option::no_delay( true ) );
416 sock.set_option( socket_option::receive_buffer_size( 65536 ) );
417 @endcode
418
419 @param opt The option to set.
420
421 @throws std::logic_error if the socket is not open.
422 @throws std::system_error on failure.
423 */
424 template<class Option>
425 60x void set_option(Option const& opt)
426 {
427 60x if (!is_open())
428 detail::throw_logic_error("set_option: socket not open");
429 60x std::error_code ec = get().set_option(
430 Option::level(), Option::name(), opt.data(), opt.size());
431 60x if (ec)
432 detail::throw_system_error(ec, "tcp_socket::set_option");
433 60x }
434
435 /** Get a socket option.
436
437 Retrieves the current value of a type-safe socket option.
438
439 @par Example
440 @code
441 auto nd = sock.get_option<socket_option::no_delay>();
442 if ( nd.value() )
443 // Nagle's algorithm is disabled
444 @endcode
445
446 @return The current option value.
447
448 @throws std::logic_error if the socket is not open.
449 @throws std::system_error on failure.
450 */
451 template<class Option>
452 62x Option get_option() const
453 {
454 62x if (!is_open())
455 detail::throw_logic_error("get_option: socket not open");
456 62x Option opt{};
457 62x std::size_t sz = opt.size();
458 std::error_code ec =
459 62x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
460 62x if (ec)
461 detail::throw_system_error(ec, "tcp_socket::get_option");
462 62x opt.resize(sz);
463 62x return opt;
464 }
465
466 /** Get the local endpoint of the socket.
467
468 Returns the local address and port to which the socket is bound.
469 For a connected socket, this is the local side of the connection.
470 The endpoint is cached when the connection is established.
471
472 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
473 the socket is not connected.
474
475 @par Thread Safety
476 The cached endpoint value is set during connect/accept completion
477 and cleared during close(). This function may be called concurrently
478 with I/O operations, but must not be called concurrently with
479 connect(), accept(), or close().
480 */
481 endpoint local_endpoint() const noexcept;
482
483 /** Get the remote endpoint of the socket.
484
485 Returns the remote address and port to which the socket is connected.
486 The endpoint is cached when the connection is established.
487
488 @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
489 the socket is not connected.
490
491 @par Thread Safety
492 The cached endpoint value is set during connect/accept completion
493 and cleared during close(). This function may be called concurrently
494 with I/O operations, but must not be called concurrently with
495 connect(), accept(), or close().
496 */
497 endpoint remote_endpoint() const noexcept;
498
499 protected:
500 10x tcp_socket() noexcept = default;
501
502 explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
503
504 private:
505 friend class tcp_acceptor;
506
507 /// Open the socket for the given protocol triple.
508 void open_for_family(int family, int type, int protocol);
509
510 37497x inline implementation& get() const noexcept
511 {
512 37497x return *static_cast<implementation*>(h_.get());
513 }
514 };
515
516 } // namespace boost::corosio
517
518 #endif
519