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