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