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