Sockets
The tcp_socket class provides asynchronous TCP networking. It supports
connecting to servers, reading and writing data, and graceful connection
management.
|
Code snippets assume:
|
Overview
A socket represents one end of a TCP connection:
corosio::tcp_socket s(ioc);
s.open();
auto [ec] = co_await s.connect(
corosio::endpoint(boost::urls::ipv4_address::loopback(), 8080));
char buf[1024];
auto [read_ec, n] = co_await s.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
Construction
Sockets are constructed from an execution context or executor:
// From io_context
corosio::tcp_socket s1(ioc);
// From executor
auto ex = ioc.get_executor();
corosio::tcp_socket s2(ex);
The socket doesn’t own system resources until open() is called.
Opening and Closing
open()
Creates the underlying TCP socket:
s.open(); // Creates IPv4 TCP socket, associates with IOCP
This allocates a socket handle and registers it with the I/O backend.
Throws std::system_error on failure.
Connecting
The connect() operation initiates a TCP connection:
auto [ec] = co_await s.connect(endpoint);
This returns an io_result<> that you can unpack with structured bindings.
Connection Errors
Common error conditions:
| Error | Meaning |
|---|---|
|
No server listening at the endpoint |
|
Connection attempt timed out |
|
No route to the host |
|
Cancelled via |
Exception Pattern
For simpler code when errors are fatal:
(co_await s.connect(endpoint)).value(); // Throws on error
Range-Based Connect
When connecting to a hostname, the resolver may return multiple endpoints
(IPv4, IPv6, multiple A records). The free function corosio::connect() tries
each in order, returning on the first success:
#include <boost/corosio/connect.hpp>
corosio::resolver r(ioc);
auto [rec, results] = co_await r.resolve("www.boost.org", "80");
if (rec)
co_return;
corosio::tcp_socket s(ioc);
auto [cec, ep] = co_await corosio::connect(s, results);
if (cec)
co_return;
// `ep` is the endpoint that accepted the connection.
Between attempts the socket is closed so the next connect() auto-opens with
the correct address family. This lets a single call try IPv4 and IPv6
candidates transparently.
The signature is generic over any range whose elements convert to the socket’s endpoint type:
template<class Socket, std::ranges::input_range Range>
requires std::convertible_to<
std::ranges::range_reference_t<Range>,
typename Socket::endpoint_type>
capy::task<capy::io_result<typename Socket::endpoint_type>>
connect(Socket& s, Range endpoints);
On success, returns the connected endpoint. On all-fail, returns the error
from the last attempt. On empty range (or when a connect condition rejects
every candidate), returns std::errc::no_such_device_or_address.
Filtering Candidates
A second overload accepts a predicate invoked as cond(last_ec, ep) before
each attempt. Returning false skips the candidate:
auto [ec, ep] = co_await corosio::connect(
s,
results,
[](std::error_code const&, corosio::endpoint const& e) {
return e.is_v4(); // IPv4 only.
});
Iterator Overload
An iterator-pair overload returns the iterator to the successful endpoint on
success, or end on failure:
auto [ec, it] = co_await corosio::connect(s, v.begin(), v.end());
if (!ec)
std::cout << "connected to index " << (it - v.begin()) << "\n";
Both overloads accept an optional connect condition as a trailing argument.
Reading Data
read_some()
Reads available data:
char buf[1024];
auto [ec, n] = co_await s.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
This completes when any data is available. The returned n may be less
than the buffer size.
End of Stream
When the peer closes the connection:
auto [ec, n] = co_await s.read_some(buf);
if (ec == capy::error::eof)
// Connection closed normally
if (n == 0 && !ec)
// Also indicates EOF in some cases
Reading Exact Amounts
Use the corosio::read() free function to fill a buffer completely:
auto [ec, n] = co_await corosio::read(s, buf);
// n == buffer_size(buf) or error occurred
See Composed Operations for details.
Writing Data
Cancellation
Move Semantics
Sockets are move-only:
corosio::tcp_socket s1(ioc);
corosio::tcp_socket s2 = std::move(s1); // OK
corosio::tcp_socket s3 = s2; // Error: deleted copy constructor
Move assignment closes any existing socket:
s1 = std::move(s2); // Closes s1's socket if open, then moves s2
| After a move, the destination uses the source’s execution context. |
The io_stream Interface
socket inherits from io_stream, which provides:
class io_stream : public io_object
{
public:
template<class MutableBufferSequence>
auto read_some(MutableBufferSequence const& buffers);
template<class ConstBufferSequence>
auto write_some(ConstBufferSequence const& buffers);
};
This enables polymorphic use:
capy::task<void> send_data(corosio::io_stream& stream)
{
co_await corosio::write(stream, some_buffer);
}
// Works with socket, wolfssl_stream, or any io_stream
corosio::tcp_socket sock(ioc);
co_await send_data(sock);
Buffer Sequences
Read and write operations accept buffer sequences:
// Single buffer
capy::mutable_buffer buf(data, size);
co_await s.read_some(buf);
// Multiple buffers (scatter/gather I/O)
std::array<capy::mutable_buffer, 2> bufs = {
capy::mutable_buffer(header, header_size),
capy::mutable_buffer(body, body_size)
};
co_await s.read_some(bufs);
See Buffer Sequences for details.
Thread Safety
| Operation | Thread Safety |
|---|---|
Distinct sockets |
Safe to use from different threads |
Same socket |
NOT safe for concurrent operations of the same type |
You can have one read and one write in flight simultaneously on the same socket. But don’t start two reads or two writes concurrently.
Example: Echo Client
capy::task<void> echo_client(corosio::io_context& ioc)
{
corosio::tcp_socket s(ioc);
s.open();
(co_await s.connect(
corosio::endpoint(boost::urls::ipv4_address::loopback(), 8080))).value();
std::string msg = "Hello, server!";
(co_await corosio::write(
s, capy::const_buffer(msg.data(), msg.size()))).value();
char buf[1024];
auto [ec, n] = co_await s.read_some(
capy::mutable_buffer(buf, sizeof(buf)));
if (!ec)
std::cout << "Server replied: "
<< std::string_view(buf, n) << "\n";
}
Next Steps
-
Acceptors — Accept incoming connections
-
Endpoints — IP addresses and ports
-
Composed Operations — read() and write()
-
Echo Server Tutorial — Server example