1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
// Copyright (c) 2026 Steve Gerbino
3  
// Copyright (c) 2026 Steve Gerbino
4  
//
4  
//
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  
//
7  
//
8  
// Official repository: https://github.com/cppalliance/corosio
8  
// Official repository: https://github.com/cppalliance/corosio
9  
//
9  
//
10  

10  

11  
#ifndef BOOST_COROSIO_TCP_SOCKET_HPP
11  
#ifndef BOOST_COROSIO_TCP_SOCKET_HPP
12  
#define BOOST_COROSIO_TCP_SOCKET_HPP
12  
#define BOOST_COROSIO_TCP_SOCKET_HPP
13  

13  

14  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/config.hpp>
15  
#include <boost/corosio/detail/platform.hpp>
15  
#include <boost/corosio/detail/platform.hpp>
16  
#include <boost/corosio/detail/except.hpp>
16  
#include <boost/corosio/detail/except.hpp>
17  
#include <boost/corosio/io/io_stream.hpp>
17  
#include <boost/corosio/io/io_stream.hpp>
18  
#include <boost/capy/io_result.hpp>
18  
#include <boost/capy/io_result.hpp>
19  
#include <boost/corosio/io_buffer_param.hpp>
19  
#include <boost/corosio/io_buffer_param.hpp>
20 -
#include <boost/corosio/tcp.hpp>
 
21  
#include <boost/corosio/endpoint.hpp>
20  
#include <boost/corosio/endpoint.hpp>
22  
#include <boost/capy/ex/executor_ref.hpp>
21  
#include <boost/capy/ex/executor_ref.hpp>
23  
#include <boost/capy/ex/execution_context.hpp>
22  
#include <boost/capy/ex/execution_context.hpp>
24  
#include <boost/capy/ex/io_env.hpp>
23  
#include <boost/capy/ex/io_env.hpp>
25  
#include <boost/capy/concept/executor.hpp>
24  
#include <boost/capy/concept/executor.hpp>
26  

25  

27  
#include <system_error>
26  
#include <system_error>
28  

27  

29  
#include <concepts>
28  
#include <concepts>
30  
#include <coroutine>
29  
#include <coroutine>
31  
#include <cstddef>
30  
#include <cstddef>
32  
#include <memory>
31  
#include <memory>
33  
#include <stop_token>
32  
#include <stop_token>
34  
#include <type_traits>
33  
#include <type_traits>
35  

34  

36  
namespace boost::corosio {
35  
namespace boost::corosio {
37  

36  

38  
#if BOOST_COROSIO_HAS_IOCP
37  
#if BOOST_COROSIO_HAS_IOCP
39  
using native_handle_type = std::uintptr_t; // SOCKET
38  
using native_handle_type = std::uintptr_t; // SOCKET
40  
#else
39  
#else
41  
using native_handle_type = int;
40  
using native_handle_type = int;
42  
#endif
41  
#endif
43  

42  

44  
/** An asynchronous TCP socket for coroutine I/O.
43  
/** An asynchronous TCP socket for coroutine I/O.
45  

44  

46  
    This class provides asynchronous TCP socket operations that return
45  
    This class provides asynchronous TCP socket operations that return
47  
    awaitable types. Each operation participates in the affine awaitable
46  
    awaitable types. Each operation participates in the affine awaitable
48  
    protocol, ensuring coroutines resume on the correct executor.
47  
    protocol, ensuring coroutines resume on the correct executor.
49  

48  

50  
    The socket must be opened before performing I/O operations. Operations
49  
    The socket must be opened before performing I/O operations. Operations
51  
    support cancellation through `std::stop_token` via the affine protocol,
50  
    support cancellation through `std::stop_token` via the affine protocol,
52  
    or explicitly through the `cancel()` member function.
51  
    or explicitly through the `cancel()` member function.
53  

52  

54  
    @par Thread Safety
53  
    @par Thread Safety
55  
    Distinct objects: Safe.@n
54  
    Distinct objects: Safe.@n
56  
    Shared objects: Unsafe. A socket must not have concurrent operations
55  
    Shared objects: Unsafe. A socket must not have concurrent operations
57  
    of the same type (e.g., two simultaneous reads). One read and one
56  
    of the same type (e.g., two simultaneous reads). One read and one
58  
    write may be in flight simultaneously.
57  
    write may be in flight simultaneously.
59  

58  

60  
    @par Semantics
59  
    @par Semantics
61  
    Wraps the platform TCP/IP stack. Operations dispatch to
60  
    Wraps the platform TCP/IP stack. Operations dispatch to
62  
    OS socket APIs via the io_context reactor (epoll, IOCP,
61  
    OS socket APIs via the io_context reactor (epoll, IOCP,
63  
    kqueue). Satisfies @ref capy::Stream.
62  
    kqueue). Satisfies @ref capy::Stream.
64  

63  

65  
    @par Example
64  
    @par Example
66  
    @code
65  
    @code
67  
    io_context ioc;
66  
    io_context ioc;
68  
    tcp_socket s(ioc);
67  
    tcp_socket s(ioc);
69  
    s.open();
68  
    s.open();
70  

69  

71  
    // Using structured bindings
70  
    // Using structured bindings
72  
    auto [ec] = co_await s.connect(
71  
    auto [ec] = co_await s.connect(
73  
        endpoint(ipv4_address::loopback(), 8080));
72  
        endpoint(ipv4_address::loopback(), 8080));
74  
    if (ec)
73  
    if (ec)
75  
        co_return;
74  
        co_return;
76  

75  

77  
    char buf[1024];
76  
    char buf[1024];
78  
    auto [read_ec, n] = co_await s.read_some(
77  
    auto [read_ec, n] = co_await s.read_some(
79  
        capy::mutable_buffer(buf, sizeof(buf)));
78  
        capy::mutable_buffer(buf, sizeof(buf)));
80  
    @endcode
79  
    @endcode
81  
*/
80  
*/
82  
class BOOST_COROSIO_DECL tcp_socket : public io_stream
81  
class BOOST_COROSIO_DECL tcp_socket : public io_stream
83  
{
82  
{
84  
public:
83  
public:
85  
    /** Different ways a socket may be shutdown. */
84  
    /** Different ways a socket may be shutdown. */
86  
    enum shutdown_type
85  
    enum shutdown_type
87  
    {
86  
    {
88  
        shutdown_receive,
87  
        shutdown_receive,
89  
        shutdown_send,
88  
        shutdown_send,
90  
        shutdown_both
89  
        shutdown_both
91  
    };
90  
    };
92  

91  

 
92 +
    /** Options for SO_LINGER socket option. */
 
93 +
    struct linger_options
 
94 +
    {
 
95 +
        bool enabled = false;
 
96 +
        int timeout  = 0; // seconds
 
97 +
    };
 
98 +

93  
    struct implementation : io_stream::implementation
99  
    struct implementation : io_stream::implementation
94  
    {
100  
    {
95  
        virtual std::coroutine_handle<> connect(
101  
        virtual std::coroutine_handle<> connect(
96  
            std::coroutine_handle<>,
102  
            std::coroutine_handle<>,
97  
            capy::executor_ref,
103  
            capy::executor_ref,
98  
            endpoint,
104  
            endpoint,
99  
            std::stop_token,
105  
            std::stop_token,
100  
            std::error_code*) = 0;
106  
            std::error_code*) = 0;
101  

107  

102  
        virtual std::error_code shutdown(shutdown_type) noexcept = 0;
108  
        virtual std::error_code shutdown(shutdown_type) noexcept = 0;
103  

109  

104  
        virtual native_handle_type native_handle() const noexcept = 0;
110  
        virtual native_handle_type native_handle() const noexcept = 0;
105  

111  

106  
        /** Request cancellation of pending asynchronous operations.
112  
        /** Request cancellation of pending asynchronous operations.
107  

113  

108  
            All outstanding operations complete with operation_canceled error.
114  
            All outstanding operations complete with operation_canceled error.
109  
            Check `ec == cond::canceled` for portable comparison.
115  
            Check `ec == cond::canceled` for portable comparison.
110  
        */
116  
        */
111  
        virtual void cancel() noexcept = 0;
117  
        virtual void cancel() noexcept = 0;
112  

118  

113 -
        /** Set a socket option.
119 +
        // Socket options
 
120 +
        virtual std::error_code set_no_delay(bool value) noexcept = 0;
 
121 +
        virtual bool no_delay(std::error_code& ec) const noexcept = 0;
114  

122  

115 -
            @param level The protocol level (e.g. `SOL_SOCKET`).
123 +
        virtual std::error_code set_keep_alive(bool value) noexcept = 0;
116 -
            @param optname The option name (e.g. `SO_KEEPALIVE`).
124 +
        virtual bool keep_alive(std::error_code& ec) const noexcept = 0;
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  

125 -
        /** Get a socket option.
126 +
        virtual std::error_code set_receive_buffer_size(int size) noexcept  = 0;
 
127 +
        virtual int receive_buffer_size(std::error_code& ec) const noexcept = 0;
126  

128  

127 -
            @param level The protocol level (e.g. `SOL_SOCKET`).
129 +
        virtual std::error_code set_send_buffer_size(int size) noexcept  = 0;
128 -
            @param optname The option name (e.g. `SO_KEEPALIVE`).
130 +
        virtual int send_buffer_size(std::error_code& ec) const noexcept = 0;
129 -
            @param data Pointer to receive the option value.
131 +

130 -
            @param size On entry, the size of the buffer. On exit,
132 +
        virtual std::error_code
131 -
                the size of the option value.
133 +
        set_linger(bool enabled, int timeout) noexcept                    = 0;
132 -
            @return Error code on failure, empty on success.
134 +
        virtual linger_options linger(std::error_code& ec) const noexcept = 0;
133 -
        */
 
134 -
        virtual std::error_code get_option(
 
135 -
            int level, int optname,
 
136 -
            void* data, std::size_t* size) const noexcept = 0;
 
137  

135  

138  
        /// Returns the cached local endpoint.
136  
        /// Returns the cached local endpoint.
139  
        virtual endpoint local_endpoint() const noexcept = 0;
137  
        virtual endpoint local_endpoint() const noexcept = 0;
140  

138  

141  
        /// Returns the cached remote endpoint.
139  
        /// Returns the cached remote endpoint.
142  
        virtual endpoint remote_endpoint() const noexcept = 0;
140  
        virtual endpoint remote_endpoint() const noexcept = 0;
143  
    };
141  
    };
144  

142  

145  
    struct connect_awaitable
143  
    struct connect_awaitable
146  
    {
144  
    {
147  
        tcp_socket& s_;
145  
        tcp_socket& s_;
148  
        endpoint endpoint_;
146  
        endpoint endpoint_;
149  
        std::stop_token token_;
147  
        std::stop_token token_;
150  
        mutable std::error_code ec_;
148  
        mutable std::error_code ec_;
151  

149  

152  
        connect_awaitable(tcp_socket& s, endpoint ep) noexcept
150  
        connect_awaitable(tcp_socket& s, endpoint ep) noexcept
153  
            : s_(s)
151  
            : s_(s)
154  
            , endpoint_(ep)
152  
            , endpoint_(ep)
155  
        {
153  
        {
156  
        }
154  
        }
157  

155  

158  
        bool await_ready() const noexcept
156  
        bool await_ready() const noexcept
159  
        {
157  
        {
160  
            return token_.stop_requested();
158  
            return token_.stop_requested();
161  
        }
159  
        }
162  

160  

163  
        capy::io_result<> await_resume() const noexcept
161  
        capy::io_result<> await_resume() const noexcept
164  
        {
162  
        {
165  
            if (token_.stop_requested())
163  
            if (token_.stop_requested())
166  
                return {make_error_code(std::errc::operation_canceled)};
164  
                return {make_error_code(std::errc::operation_canceled)};
167  
            return {ec_};
165  
            return {ec_};
168  
        }
166  
        }
169  

167  

170  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
168  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
171  
            -> std::coroutine_handle<>
169  
            -> std::coroutine_handle<>
172  
        {
170  
        {
173  
            token_ = env->stop_token;
171  
            token_ = env->stop_token;
174  
            return s_.get().connect(h, env->executor, endpoint_, token_, &ec_);
172  
            return s_.get().connect(h, env->executor, endpoint_, token_, &ec_);
175  
        }
173  
        }
176  
    };
174  
    };
177  

175  

178  
public:
176  
public:
179  
    /** Destructor.
177  
    /** Destructor.
180  

178  

181  
        Closes the socket if open, cancelling any pending operations.
179  
        Closes the socket if open, cancelling any pending operations.
182  
    */
180  
    */
183  
    ~tcp_socket() override;
181  
    ~tcp_socket() override;
184  

182  

185  
    /** Construct a socket from an execution context.
183  
    /** Construct a socket from an execution context.
186  

184  

187  
        @param ctx The execution context that will own this socket.
185  
        @param ctx The execution context that will own this socket.
188  
    */
186  
    */
189  
    explicit tcp_socket(capy::execution_context& ctx);
187  
    explicit tcp_socket(capy::execution_context& ctx);
190  

188  

191  
    /** Construct a socket from an executor.
189  
    /** Construct a socket from an executor.
192  

190  

193  
        The socket is associated with the executor's context.
191  
        The socket is associated with the executor's context.
194  

192  

195  
        @param ex The executor whose context will own the socket.
193  
        @param ex The executor whose context will own the socket.
196  
    */
194  
    */
197  
    template<class Ex>
195  
    template<class Ex>
198  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_socket>) &&
196  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_socket>) &&
199  
        capy::Executor<Ex>
197  
        capy::Executor<Ex>
200  
    explicit tcp_socket(Ex const& ex) : tcp_socket(ex.context())
198  
    explicit tcp_socket(Ex const& ex) : tcp_socket(ex.context())
201  
    {
199  
    {
202  
    }
200  
    }
203  

201  

204  
    /** Move constructor.
202  
    /** Move constructor.
205  

203  

206  
        Transfers ownership of the socket resources.
204  
        Transfers ownership of the socket resources.
207  

205  

208  
        @param other The socket to move from.
206  
        @param other The socket to move from.
209  

207  

210  
        @pre No awaitables returned by @p other's methods exist.
208  
        @pre No awaitables returned by @p other's methods exist.
211  
        @pre @p other is not referenced as a peer in any outstanding
209  
        @pre @p other is not referenced as a peer in any outstanding
212  
            accept awaitable.
210  
            accept awaitable.
213  
        @pre The execution context associated with @p other must
211  
        @pre The execution context associated with @p other must
214  
            outlive this socket.
212  
            outlive this socket.
215  
    */
213  
    */
216  
    tcp_socket(tcp_socket&& other) noexcept : io_object(std::move(other)) {}
214  
    tcp_socket(tcp_socket&& other) noexcept : io_object(std::move(other)) {}
217  

215  

218  
    /** Move assignment operator.
216  
    /** Move assignment operator.
219  

217  

220  
        Closes any existing socket and transfers ownership.
218  
        Closes any existing socket and transfers ownership.
221  

219  

222  
        @param other The socket to move from.
220  
        @param other The socket to move from.
223  

221  

224  
        @pre No awaitables returned by either `*this` or @p other's
222  
        @pre No awaitables returned by either `*this` or @p other's
225  
            methods exist.
223  
            methods exist.
226  
        @pre Neither `*this` nor @p other is referenced as a peer in
224  
        @pre Neither `*this` nor @p other is referenced as a peer in
227  
            any outstanding accept awaitable.
225  
            any outstanding accept awaitable.
228  
        @pre The execution context associated with @p other must
226  
        @pre The execution context associated with @p other must
229  
            outlive this socket.
227  
            outlive this socket.
230  

228  

231  
        @return Reference to this socket.
229  
        @return Reference to this socket.
232  
    */
230  
    */
233  
    tcp_socket& operator=(tcp_socket&& other) noexcept
231  
    tcp_socket& operator=(tcp_socket&& other) noexcept
234  
    {
232  
    {
235  
        if (this != &other)
233  
        if (this != &other)
236  
        {
234  
        {
237  
            close();
235  
            close();
238  
            h_ = std::move(other.h_);
236  
            h_ = std::move(other.h_);
239  
        }
237  
        }
240  
        return *this;
238  
        return *this;
241  
    }
239  
    }
242  

240  

243  
    tcp_socket(tcp_socket const&)            = delete;
241  
    tcp_socket(tcp_socket const&)            = delete;
244  
    tcp_socket& operator=(tcp_socket const&) = delete;
242  
    tcp_socket& operator=(tcp_socket const&) = delete;
245  

243  

246  
    /** Open the socket.
244  
    /** Open the socket.
247  

245  

248 -
        Creates a TCP socket and associates it with the platform
246 +
        Creates an IPv4 TCP socket and associates it with the platform
249 -
        reactor (IOCP on Windows). Calling @ref connect on a closed
247 +
        reactor (IOCP on Windows). This must be called before initiating
250 -
        socket opens it automatically with the endpoint's address family,
248 +
        I/O operations.
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  

249  

257  
        @throws std::system_error on failure.
250  
        @throws std::system_error on failure.
258  
    */
251  
    */
259 -
    void open( tcp proto = tcp::v4() );
252 +
    void open();
260  

253  

261  
    /** Close the socket.
254  
    /** Close the socket.
262  

255  

263  
        Releases socket resources. Any pending operations complete
256  
        Releases socket resources. Any pending operations complete
264  
        with `errc::operation_canceled`.
257  
        with `errc::operation_canceled`.
265  
    */
258  
    */
266  
    void close();
259  
    void close();
267  

260  

268  
    /** Check if the socket is open.
261  
    /** Check if the socket is open.
269  

262  

270  
        @return `true` if the socket is open and ready for operations.
263  
        @return `true` if the socket is open and ready for operations.
271  
    */
264  
    */
272  
    bool is_open() const noexcept
265  
    bool is_open() const noexcept
273  
    {
266  
    {
274  
#if BOOST_COROSIO_HAS_IOCP
267  
#if BOOST_COROSIO_HAS_IOCP
275  
        return h_ && get().native_handle() != ~native_handle_type(0);
268  
        return h_ && get().native_handle() != ~native_handle_type(0);
276  
#else
269  
#else
277  
        return h_ && get().native_handle() >= 0;
270  
        return h_ && get().native_handle() >= 0;
278  
#endif
271  
#endif
279  
    }
272  
    }
280  

273  

281  
    /** Initiate an asynchronous connect operation.
274  
    /** Initiate an asynchronous connect operation.
282  

275  

283 -
        If the socket is not already open, it is opened automatically
276 +
        Connects the socket to the specified remote endpoint. The socket
284 -
        using the address family of @p ep (IPv4 or IPv6). If the socket
277 +
        must be open before calling this function.
285 -
        is already open, the existing file descriptor is used as-is.
 
286  

278  

287  
        The operation supports cancellation via `std::stop_token` through
279  
        The operation supports cancellation via `std::stop_token` through
288  
        the affine awaitable protocol. If the associated stop token is
280  
        the affine awaitable protocol. If the associated stop token is
289  
        triggered, the operation completes immediately with
281  
        triggered, the operation completes immediately with
290  
        `errc::operation_canceled`.
282  
        `errc::operation_canceled`.
291  

283  

292  
        @param ep The remote endpoint to connect to.
284  
        @param ep The remote endpoint to connect to.
293  

285  

294  
        @return An awaitable that completes with `io_result<>`.
286  
        @return An awaitable that completes with `io_result<>`.
295  
            Returns success (default error_code) on successful connection,
287  
            Returns success (default error_code) on successful connection,
296  
            or an error code on failure including:
288  
            or an error code on failure including:
297  
            - connection_refused: No server listening at endpoint
289  
            - connection_refused: No server listening at endpoint
298  
            - timed_out: Connection attempt timed out
290  
            - timed_out: Connection attempt timed out
299  
            - network_unreachable: No route to host
291  
            - network_unreachable: No route to host
300  
            - operation_canceled: Cancelled via stop_token or cancel().
292  
            - operation_canceled: Cancelled via stop_token or cancel().
301  
                Check `ec == cond::canceled` for portable comparison.
293  
                Check `ec == cond::canceled` for portable comparison.
302  

294  

303 -
        @throws std::system_error if the socket needs to be opened
295 +
        @throws std::logic_error if the socket is not open.
304 -
            and the open fails.
 
305  

296  

306  
        @par Preconditions
297  
        @par Preconditions
 
298 +
        The socket must be open (`is_open() == true`).
 
299 +

307  
        This socket must outlive the returned awaitable.
300  
        This socket must outlive the returned awaitable.
308  

301  

309  
        @par Example
302  
        @par Example
310 -
        // Socket opened automatically with correct address family:
 
311  
        @code
303  
        @code
312  
        auto [ec] = co_await s.connect(endpoint);
304  
        auto [ec] = co_await s.connect(endpoint);
313  
        if (ec) { ... }
305  
        if (ec) { ... }
314  
        @endcode
306  
        @endcode
315  
    */
307  
    */
316  
    auto connect(endpoint ep)
308  
    auto connect(endpoint ep)
317  
    {
309  
    {
318  
        if (!is_open())
310  
        if (!is_open())
319 -
            open(ep.is_v6() ? tcp::v6() : tcp::v4());
311 +
            detail::throw_logic_error("connect: socket not open");
320  
        return connect_awaitable(*this, ep);
312  
        return connect_awaitable(*this, ep);
321  
    }
313  
    }
322  

314  

323  
    /** Cancel any pending asynchronous operations.
315  
    /** Cancel any pending asynchronous operations.
324  

316  

325  
        All outstanding operations complete with `errc::operation_canceled`.
317  
        All outstanding operations complete with `errc::operation_canceled`.
326  
        Check `ec == cond::canceled` for portable comparison.
318  
        Check `ec == cond::canceled` for portable comparison.
327  
    */
319  
    */
328  
    void cancel();
320  
    void cancel();
329  

321  

330  
    /** Get the native socket handle.
322  
    /** Get the native socket handle.
331  

323  

332  
        Returns the underlying platform-specific socket descriptor.
324  
        Returns the underlying platform-specific socket descriptor.
333  
        On POSIX systems this is an `int` file descriptor.
325  
        On POSIX systems this is an `int` file descriptor.
334  
        On Windows this is a `SOCKET` handle.
326  
        On Windows this is a `SOCKET` handle.
335  

327  

336  
        @return The native socket handle, or -1/INVALID_SOCKET if not open.
328  
        @return The native socket handle, or -1/INVALID_SOCKET if not open.
337  

329  

338  
        @par Preconditions
330  
        @par Preconditions
339  
        None. May be called on closed sockets.
331  
        None. May be called on closed sockets.
340  
    */
332  
    */
341  
    native_handle_type native_handle() const noexcept;
333  
    native_handle_type native_handle() const noexcept;
342  

334  

343  
    /** Disable sends or receives on the socket.
335  
    /** Disable sends or receives on the socket.
344  

336  

345  
        TCP connections are full-duplex: each direction (send and receive)
337  
        TCP connections are full-duplex: each direction (send and receive)
346  
        operates independently. This function allows you to close one or
338  
        operates independently. This function allows you to close one or
347  
        both directions without destroying the socket.
339  
        both directions without destroying the socket.
348  

340  

349  
        @li @ref shutdown_send sends a TCP FIN packet to the peer,
341  
        @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
342  
            signaling that you have no more data to send. You can still
351  
            receive data until the peer also closes their send direction.
343  
            receive data until the peer also closes their send direction.
352  
            This is the most common use case, typically called before
344  
            This is the most common use case, typically called before
353  
            close() to ensure graceful connection termination.
345  
            close() to ensure graceful connection termination.
354  

346  

355  
        @li @ref shutdown_receive disables reading on the socket. This
347  
        @li @ref shutdown_receive disables reading on the socket. This
356  
            does NOT send anything to the peer - they are not informed
348  
            does NOT send anything to the peer - they are not informed
357  
            and may continue sending data. Subsequent reads will fail
349  
            and may continue sending data. Subsequent reads will fail
358  
            or return end-of-file. Incoming data may be discarded or
350  
            or return end-of-file. Incoming data may be discarded or
359  
            buffered depending on the operating system.
351  
            buffered depending on the operating system.
360  

352  

361  
        @li @ref shutdown_both combines both effects: sends a FIN and
353  
        @li @ref shutdown_both combines both effects: sends a FIN and
362  
            disables reading.
354  
            disables reading.
363  

355  

364  
        When the peer shuts down their send direction (sends a FIN),
356  
        When the peer shuts down their send direction (sends a FIN),
365  
        subsequent read operations will complete with `capy::cond::eof`.
357  
        subsequent read operations will complete with `capy::cond::eof`.
366  
        Use the portable condition test rather than comparing error
358  
        Use the portable condition test rather than comparing error
367  
        codes directly:
359  
        codes directly:
368  

360  

369  
        @code
361  
        @code
370  
        auto [ec, n] = co_await sock.read_some(buffer);
362  
        auto [ec, n] = co_await sock.read_some(buffer);
371  
        if (ec == capy::cond::eof)
363  
        if (ec == capy::cond::eof)
372  
        {
364  
        {
373  
            // Peer closed their send direction
365  
            // Peer closed their send direction
374  
        }
366  
        }
375  
        @endcode
367  
        @endcode
376  

368  

377  
        Any error from the underlying system call is silently discarded
369  
        Any error from the underlying system call is silently discarded
378  
        because it is unlikely to be helpful.
370  
        because it is unlikely to be helpful.
379  

371  

380  
        @param what Determines what operations will no longer be allowed.
372  
        @param what Determines what operations will no longer be allowed.
381  
    */
373  
    */
382  
    void shutdown(shutdown_type what);
374  
    void shutdown(shutdown_type what);
383  

375  

384 -
    /** Set a socket option.
376 +
    //
 
377 +
    // Socket Options
 
378 +
    //
385  

379  

386 -
        Applies a type-safe socket option to the underlying socket.
380 +
    /** Enable or disable TCP_NODELAY (disable Nagle's algorithm).
387 -
        The option type encodes the protocol level and option name.
 
388  

381  

389 -
        @par Example
382 +
        When enabled, segments are sent as soon as possible even if
390 -
        @code
383 +
        there is only a small amount of data. This reduces latency
391 -
        sock.set_option( socket_option::no_delay( true ) );
384 +
        at the potential cost of increased network traffic.
392 -
        sock.set_option( socket_option::receive_buffer_size( 65536 ) );
 
393 -
        @endcode
 
394  

385  

395 -
        @param opt The option to set.
386 +
        @param value `true` to disable Nagle's algorithm (enable no-delay).
396  

387  

397  
        @throws std::logic_error if the socket is not open.
388  
        @throws std::logic_error if the socket is not open.
398  
        @throws std::system_error on failure.
389  
        @throws std::system_error on failure.
399  
    */
390  
    */
400 -
    template<class Option>
391 +
    void set_no_delay(bool value);
401 -
    void set_option( Option const& opt )
 
402 -
    {
 
403 -
        if (!is_open())
 
404 -
            detail::throw_logic_error( "set_option: socket not open" );
 
405 -
        std::error_code ec = get().set_option(
 
406 -
            Option::level(), Option::name(), opt.data(), opt.size() );
 
407 -
        if (ec)
 
408 -
            detail::throw_system_error( ec, "tcp_socket::set_option" );
 
409 -
    }
 
410  

392  

411 -
    /** Get a socket option.
393 +
    /** Get the current TCP_NODELAY setting.
412  

394  

413 -
        Retrieves the current value of a type-safe socket option.
395 +
        @return `true` if Nagle's algorithm is disabled.
414  

396  

415 -
        @par Example
397 +
        @throws std::logic_error if the socket is not open.
416 -
        @code
398 +
        @throws std::system_error on failure.
417 -
        auto nd = sock.get_option<socket_option::no_delay>();
399 +
    */
418 -
        if ( nd.value() )
400 +
    bool no_delay() const;
419 -
            // Nagle's algorithm is disabled
 
420 -
        @endcode
 
421  

401  

422 -
        @return The current option value.
402 +
    /** Enable or disable SO_KEEPALIVE.
 
403 +

 
404 +
        When enabled, the socket will periodically send keepalive probes
 
405 +
        to detect if the peer is still reachable.
 
406 +

 
407 +
        @param value `true` to enable keepalive probes.
423  

408  

424  
        @throws std::logic_error if the socket is not open.
409  
        @throws std::logic_error if the socket is not open.
425  
        @throws std::system_error on failure.
410  
        @throws std::system_error on failure.
426  
    */
411  
    */
427 -
    template<class Option>
412 +
    void set_keep_alive(bool value);
428 -
    Option get_option() const
413 +

429 -
    {
414 +
    /** Get the current SO_KEEPALIVE setting.
430 -
        if (!is_open())
415 +

431 -
            detail::throw_logic_error( "get_option: socket not open" );
416 +
        @return `true` if keepalive is enabled.
432 -
        Option opt{};
417 +

433 -
        std::size_t sz = opt.size();
418 +
        @throws std::logic_error if the socket is not open.
434 -
        std::error_code ec = get().get_option(
419 +
        @throws std::system_error on failure.
435 -
            Option::level(), Option::name(), opt.data(), &sz );
420 +
    */
436 -
        if (ec)
421 +
    bool keep_alive() const;
437 -
            detail::throw_system_error( ec, "tcp_socket::get_option" );
422 +

438 -
        opt.resize( sz );
423 +
    /** Set the receive buffer size (SO_RCVBUF).
439 -
        return opt;
424 +

440 -
    }
425 +
        @param size The desired receive buffer size in bytes.
 
426 +

 
427 +
        @throws std::logic_error if the socket is not open.
 
428 +
        @throws std::system_error on failure.
 
429 +

 
430 +
        @note The operating system may adjust the actual buffer size.
 
431 +
    */
 
432 +
    void set_receive_buffer_size(int size);
 
433 +

 
434 +
    /** Get the receive buffer size (SO_RCVBUF).
 
435 +

 
436 +
        @return The current receive buffer size in bytes.
 
437 +

 
438 +
        @throws std::logic_error if the socket is not open.
 
439 +
        @throws std::system_error on failure.
 
440 +
    */
 
441 +
    int receive_buffer_size() const;
 
442 +

 
443 +
    /** Set the send buffer size (SO_SNDBUF).
 
444 +

 
445 +
        @param size The desired send buffer size in bytes.
 
446 +

 
447 +
        @throws std::logic_error if the socket is not open.
 
448 +
        @throws std::system_error on failure.
 
449 +

 
450 +
        @note The operating system may adjust the actual buffer size.
 
451 +
    */
 
452 +
    void set_send_buffer_size(int size);
 
453 +

 
454 +
    /** Get the send buffer size (SO_SNDBUF).
 
455 +

 
456 +
        @return The current send buffer size in bytes.
 
457 +

 
458 +
        @throws std::logic_error if the socket is not open.
 
459 +
        @throws std::system_error on failure.
 
460 +
    */
 
461 +
    int send_buffer_size() const;
 
462 +

 
463 +
    /** Set the SO_LINGER option.
 
464 +

 
465 +
        Controls behavior when closing a socket with unsent data.
 
466 +

 
467 +
        @param enabled If `true`, close() will block until data is sent
 
468 +
            or the timeout expires. If `false`, close() returns immediately.
 
469 +
        @param timeout The linger timeout in seconds (only used if enabled).
 
470 +

 
471 +
        @throws std::logic_error if the socket is not open.
 
472 +
        @throws std::system_error on failure.
 
473 +
    */
 
474 +
    void set_linger(bool enabled, int timeout);
 
475 +

 
476 +
    /** Get the current SO_LINGER setting.
 
477 +

 
478 +
        @return The current linger options.
 
479 +

 
480 +
        @throws std::logic_error if the socket is not open.
 
481 +
        @throws std::system_error on failure.
 
482 +
    */
 
483 +
    linger_options linger() const;
441  

484  

442  
    /** Get the local endpoint of the socket.
485  
    /** Get the local endpoint of the socket.
443  

486  

444  
        Returns the local address and port to which the socket is bound.
487  
        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.
488  
        For a connected socket, this is the local side of the connection.
446  
        The endpoint is cached when the connection is established.
489  
        The endpoint is cached when the connection is established.
447  

490  

448  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
491  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
449  
            the socket is not connected.
492  
            the socket is not connected.
450  

493  

451  
        @par Thread Safety
494  
        @par Thread Safety
452  
        The cached endpoint value is set during connect/accept completion
495  
        The cached endpoint value is set during connect/accept completion
453  
        and cleared during close(). This function may be called concurrently
496  
        and cleared during close(). This function may be called concurrently
454  
        with I/O operations, but must not be called concurrently with
497  
        with I/O operations, but must not be called concurrently with
455  
        connect(), accept(), or close().
498  
        connect(), accept(), or close().
456  
    */
499  
    */
457  
    endpoint local_endpoint() const noexcept;
500  
    endpoint local_endpoint() const noexcept;
458  

501  

459  
    /** Get the remote endpoint of the socket.
502  
    /** Get the remote endpoint of the socket.
460  

503  

461  
        Returns the remote address and port to which the socket is connected.
504  
        Returns the remote address and port to which the socket is connected.
462  
        The endpoint is cached when the connection is established.
505  
        The endpoint is cached when the connection is established.
463  

506  

464  
        @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
507  
        @return The remote endpoint, or a default endpoint (0.0.0.0:0) if
465  
            the socket is not connected.
508  
            the socket is not connected.
466  

509  

467  
        @par Thread Safety
510  
        @par Thread Safety
468  
        The cached endpoint value is set during connect/accept completion
511  
        The cached endpoint value is set during connect/accept completion
469  
        and cleared during close(). This function may be called concurrently
512  
        and cleared during close(). This function may be called concurrently
470  
        with I/O operations, but must not be called concurrently with
513  
        with I/O operations, but must not be called concurrently with
471  
        connect(), accept(), or close().
514  
        connect(), accept(), or close().
472  
    */
515  
    */
473  
    endpoint remote_endpoint() const noexcept;
516  
    endpoint remote_endpoint() const noexcept;
474  

517  

475  
protected:
518  
protected:
476  
    tcp_socket() noexcept = default;
519  
    tcp_socket() noexcept = default;
477  

520  

478  
    explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
521  
    explicit tcp_socket(handle h) noexcept : io_object(std::move(h)) {}
479  

522  

480  
private:
523  
private:
481 -

 
482 -
    /// Open the socket for the given protocol triple.
 
483 -
    void open_for_family(int family, int type, int protocol);
 
484  
    friend class tcp_acceptor;
524  
    friend class tcp_acceptor;
485  

525  

486  
    inline implementation& get() const noexcept
526  
    inline implementation& get() const noexcept
487  
    {
527  
    {
488  
        return *static_cast<implementation*>(h_.get());
528  
        return *static_cast<implementation*>(h_.get());
489  
    }
529  
    }
490  
};
530  
};
491  

531  

492  
} // namespace boost::corosio
532  
} // namespace boost::corosio
493  

533  

494  
#endif
534  
#endif