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/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 HIT 5206 : connect_awaitable(tcp_socket& s, endpoint ep) noexcept
171 5206 : : s_(s), endpoint_(ep) {}
172 :
173 5206 : std::coroutine_handle<> dispatch(
174 : std::coroutine_handle<> h, capy::executor_ref ex) const
175 : {
176 5206 : 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 176 : 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 10 : tcp_socket& operator=(tcp_socket&& other) noexcept
236 : {
237 10 : if (this != &other)
238 : {
239 10 : close();
240 10 : h_ = std::move(other.h_);
241 : }
242 10 : 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 32085 : 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 32085 : 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 5206 : auto connect(endpoint ep)
341 : {
342 5206 : if (!is_open())
343 38 : open(ep.is_v6() ? tcp::v6() : tcp::v4());
344 5206 : 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 60 : void set_option(Option const& opt)
426 : {
427 60 : if (!is_open())
428 MIS 0 : detail::throw_logic_error("set_option: socket not open");
429 HIT 60 : std::error_code ec = get().set_option(
430 : Option::level(), Option::name(), opt.data(), opt.size());
431 60 : if (ec)
432 MIS 0 : detail::throw_system_error(ec, "tcp_socket::set_option");
433 HIT 60 : }
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 62 : Option get_option() const
453 : {
454 62 : if (!is_open())
455 MIS 0 : detail::throw_logic_error("get_option: socket not open");
456 HIT 62 : Option opt{};
457 62 : std::size_t sz = opt.size();
458 : std::error_code ec =
459 62 : get().get_option(Option::level(), Option::name(), opt.data(), &sz);
460 62 : if (ec)
461 MIS 0 : detail::throw_system_error(ec, "tcp_socket::get_option");
462 HIT 62 : opt.resize(sz);
463 62 : 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 10 : 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 37497 : inline implementation& get() const noexcept
511 : {
512 37497 : return *static_cast<implementation*>(h_.get());
513 : }
514 : };
515 :
516 : } // namespace boost::corosio
517 :
518 : #endif
|