TCP
This guide shows how to use async TCP with Vix Async.
Use this page when you want to connect to a TCP server, read and write bytes, or accept incoming TCP connections from an async server.
Public header
#include <vix/async.hpp>
#include <vix/print.hpp>What TCP provides
The TCP API is built around two interfaces.
| Type | Purpose |
|---|---|
tcp_stream | Represents a connected TCP stream. |
tcp_listener | Represents a listening TCP socket. |
A TCP stream can connect to a remote endpoint, read bytes, write bytes, close the connection, and expose the native socket handle when supported.
A TCP listener can listen on a local endpoint, accept incoming connections, and close the listener.
TCP endpoint
A TCP endpoint has a host and a port.
vix::async::net::tcp_endpoint endpoint{
"127.0.0.1",
8080
};You can use an IP address or a hostname:
vix::async::net::tcp_endpoint endpoint{
"example.com",
80
};Create a TCP stream
Use make_tcp_stream with an io_context.
auto stream = vix::async::net::make_tcp_stream(ctx);The stream is attached to the async runtime.
Connect to a server
Use async_connect.
co_await stream->async_connect({
"example.com",
80
});Complete example:
#include <vix/async.hpp>
#include <vix/print.hpp>
#include <exception>
vix::async::core::task<void> app(vix::async::core::io_context &ctx)
{
try
{
auto stream = vix::async::net::make_tcp_stream(ctx);
co_await stream->async_connect({
"example.com",
80
});
vix::print("connected =", stream->is_open() ? "yes" : "no");
stream->close();
}
catch (const std::exception &ex)
{
vix::eprint(ex.what());
}
ctx.stop();
co_return;
}
int main()
{
vix::async::core::io_context ctx;
auto t = app(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();
return 0;
}Write to a TCP stream
Use async_write with a byte buffer.
std::string request = "hello";
auto bytes = co_await stream->async_write(
std::as_bytes(std::span{request}));Example:
#include <vix/async.hpp>
#include <vix/print.hpp>
#include <exception>
#include <span>
#include <string>
vix::async::core::task<void> app(vix::async::core::io_context &ctx)
{
try
{
auto stream = vix::async::net::make_tcp_stream(ctx);
co_await stream->async_connect({
"127.0.0.1",
8080
});
std::string message = "hello from Vix";
const std::size_t bytes = co_await stream->async_write(
std::as_bytes(std::span{message}));
vix::print("written =", bytes);
stream->close();
}
catch (const std::exception &ex)
{
vix::eprint(ex.what());
}
ctx.stop();
co_return;
}
int main()
{
vix::async::core::io_context ctx;
auto t = app(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();
return 0;
}Read from a TCP stream
Use async_read with a mutable byte buffer.
std::array<std::byte, 1024> buffer{};
const std::size_t bytes = co_await stream->async_read(
std::span<std::byte>{buffer.data(), buffer.size()});Example:
#include <vix/async.hpp>
#include <vix/print.hpp>
#include <array>
#include <exception>
#include <span>
#include <string>
vix::async::core::task<void> app(vix::async::core::io_context &ctx)
{
try
{
auto stream = vix::async::net::make_tcp_stream(ctx);
co_await stream->async_connect({
"127.0.0.1",
8080
});
std::array<std::byte, 1024> buffer{};
const std::size_t bytes = co_await stream->async_read(
std::span<std::byte>{buffer.data(), buffer.size()});
std::string text;
text.resize(bytes);
for (std::size_t i = 0; i < bytes; ++i)
{
text[i] = static_cast<char>(buffer[i]);
}
vix::print("received =", text);
stream->close();
}
catch (const std::exception &ex)
{
vix::eprint(ex.what());
}
ctx.stop();
co_return;
}
int main()
{
vix::async::core::io_context ctx;
auto t = app(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();
return 0;
}HTTP request example
TCP streams can be used to write simple protocols.
This example sends a minimal HTTP request.
#include <vix/async.hpp>
#include <vix/print.hpp>
#include <array>
#include <exception>
#include <span>
#include <string>
vix::async::core::task<void> app(vix::async::core::io_context &ctx)
{
try
{
auto stream = vix::async::net::make_tcp_stream(ctx);
co_await stream->async_connect({
"example.com",
80
});
std::string request =
"GET / HTTP/1.1\r\n"
"Host: example.com\r\n"
"Connection: close\r\n"
"\r\n";
const std::size_t written = co_await stream->async_write(
std::as_bytes(std::span{request}));
vix::print("written =", written);
std::array<std::byte, 4096> buffer{};
const std::size_t read = co_await stream->async_read(
std::span<std::byte>{buffer.data(), buffer.size()});
std::string response;
response.resize(read);
for (std::size_t i = 0; i < read; ++i)
{
response[i] = static_cast<char>(buffer[i]);
}
vix::print(response);
stream->close();
}
catch (const std::exception &ex)
{
vix::eprint(ex.what());
}
ctx.stop();
co_return;
}
int main()
{
vix::async::core::io_context ctx;
auto t = app(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();
return 0;
}Create a TCP listener
Use make_tcp_listener.
auto listener = vix::async::net::make_tcp_listener(ctx);Then listen on an endpoint.
co_await listener->async_listen({
"127.0.0.1",
8080
});Accept a connection
Use async_accept.
auto client = co_await listener->async_accept();The accepted connection is returned as a std::unique_ptr<tcp_stream>.
Minimal TCP server
#include <vix/async.hpp>
#include <vix/print.hpp>
#include <array>
#include <exception>
#include <span>
#include <string>
vix::async::core::task<void> server(vix::async::core::io_context &ctx)
{
try
{
auto listener = vix::async::net::make_tcp_listener(ctx);
co_await listener->async_listen({
"127.0.0.1",
8080
});
vix::print("listening on 127.0.0.1:8080");
auto client = co_await listener->async_accept();
vix::print("client accepted");
std::string message = "hello from Vix TCP\n";
(void)co_await client->async_write(
std::as_bytes(std::span{message}));
client->close();
listener->close();
}
catch (const std::exception &ex)
{
vix::eprint(ex.what());
}
ctx.stop();
co_return;
}
int main()
{
vix::async::core::io_context ctx;
auto t = server(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();
return 0;
}Test from another terminal:
nc 127.0.0.1 8080Expected output:
hello from Vix TCPEcho server
This example accepts one client, reads data, and writes it back.
#include <vix/async.hpp>
#include <vix/print.hpp>
#include <array>
#include <exception>
#include <span>
vix::async::core::task<void> server(vix::async::core::io_context &ctx)
{
try
{
auto listener = vix::async::net::make_tcp_listener(ctx);
co_await listener->async_listen({
"127.0.0.1",
8080
});
vix::print("echo server ready");
auto client = co_await listener->async_accept();
std::array<std::byte, 1024> buffer{};
const std::size_t bytes = co_await client->async_read(
std::span<std::byte>{buffer.data(), buffer.size()});
(void)co_await client->async_write(
std::span<const std::byte>{buffer.data(), bytes});
client->close();
listener->close();
}
catch (const std::exception &ex)
{
vix::eprint(ex.what());
}
ctx.stop();
co_return;
}
int main()
{
vix::async::core::io_context ctx;
auto t = server(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();
return 0;
}Test:
printf "hello\n" | nc 127.0.0.1 8080Expected output:
helloCancellation
TCP operations accept a cancel_token.
vix::async::core::cancel_source source;
co_await stream->async_connect(endpoint, source.token());You can use cancellation with:
co_await stream->async_connect(endpoint, token);
co_await stream->async_read(buffer, token);
co_await stream->async_write(buffer, token);
co_await listener->async_accept(token);Example:
#include <vix/async.hpp>
#include <vix/print.hpp>
#include <chrono>
#include <exception>
#include <system_error>
using namespace std::chrono_literals;
vix::async::core::task<void> app(vix::async::core::io_context &ctx)
{
vix::async::core::cancel_source source;
ctx.timers().after(100ms, [&source]()
{
source.request_cancel();
});
try
{
auto stream = vix::async::net::make_tcp_stream(ctx);
co_await stream->async_connect({
"10.255.255.1",
80
}, source.token());
stream->close();
}
catch (const std::system_error &ex)
{
vix::eprint("tcp cancelled or failed:", ex.code().message());
}
catch (const std::exception &ex)
{
vix::eprint(ex.what());
}
ctx.stop();
co_return;
}
int main()
{
vix::async::core::io_context ctx;
auto t = app(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();
return 0;
}Close a stream
Use close() when the connection is no longer needed.
stream->close();Check whether it is open:
vix::print("open =", stream->is_open() ? "yes" : "no");Close a listener
Use close() when the listener should stop accepting new connections.
listener->close();Check whether it is open:
vix::print("listening =", listener->is_open() ? "yes" : "no");Native handle
tcp_stream::native_handle() returns the native socket handle when supported.
const int fd = stream->native_handle();This is mainly useful for lower-level integrations such as TLS adapters. Most application code should not need it.
TCP lifecycle
Client flow:
create io_context
create tcp_stream
connect
write or read
close stream
stop contextServer flow:
create io_context
create tcp_listener
listen
accept client
read or write
close client
close listener
stop contextTCP API summary
| API | Purpose |
|---|---|
make_tcp_stream(ctx) | Creates a TCP stream. |
make_tcp_listener(ctx) | Creates a TCP listener. |
async_connect(endpoint, token) | Connects to a remote endpoint. |
async_read(buffer, token) | Reads bytes from a stream. |
async_write(buffer, token) | Writes bytes to a stream. |
async_listen(endpoint, backlog) | Starts listening on a local endpoint. |
async_accept(token) | Accepts one incoming connection. |
close() | Closes a stream or listener. |
is_open() | Checks open state. |
native_handle() | Returns native socket handle when supported. |
Common workflows
Connect to a server
auto stream = vix::async::net::make_tcp_stream(ctx);
co_await stream->async_connect({
"example.com",
80
});Write bytes
std::string message = "hello";
const std::size_t bytes = co_await stream->async_write(
std::as_bytes(std::span{message}));Read bytes
std::array<std::byte, 1024> buffer{};
const std::size_t bytes = co_await stream->async_read(
std::span<std::byte>{buffer.data(), buffer.size()});Listen for one connection
auto listener = vix::async::net::make_tcp_listener(ctx);
co_await listener->async_listen({
"127.0.0.1",
8080
});
auto client = co_await listener->async_accept();Close resources
client->close();
listener->close();Common mistakes
Forgetting to run the context
Wrong:
auto t = app(ctx);
std::move(t).start(ctx.get_scheduler());Correct:
auto t = app(ctx);
std::move(t).start(ctx.get_scheduler());
ctx.run();Forgetting to stop the context
After the TCP flow is complete, stop the runtime.
ctx.stop();
co_return;Using TCP APIs outside a coroutine
Wrong:
stream->async_connect({"127.0.0.1", 8080});Correct:
co_await stream->async_connect({"127.0.0.1", 8080});Passing a text buffer directly to async_write
async_write expects bytes.
Wrong:
co_await stream->async_write(message);Correct:
co_await stream->async_write(
std::as_bytes(std::span{message}));Reading into an immutable buffer
async_read needs a mutable byte span.
std::array<std::byte, 1024> buffer{};
const std::size_t bytes = co_await stream->async_read(
std::span<std::byte>{buffer.data(), buffer.size()});Forgetting to close sockets
Close streams and listeners when you are done.
stream->close();
listener->close();Expecting async_read to fill the whole buffer
async_read returns the number of bytes actually read. Use the returned byte count.
Best practices
Use tcp_stream for outgoing client connections. Use tcp_listener for incoming server connections. Keep buffers alive until the awaited operation completes. Use std::as_bytes for write buffers. Use mutable std::span<std::byte> for read buffers. Handle std::system_error around network operations. Use cancellation tokens for long-running connect, read, write, and accept operations. Close streams and listeners explicitly when finished. Stop the io_context when the top-level TCP flow is complete.
Related pages
| Page | Purpose |
|---|---|
| io_context | Learn the runtime context. |
| Tasks | Learn coroutine tasks. |
| Timers | Learn timeout-style flows. |
| Cancellation | Learn cancellation tokens. |
| UDP | Learn UDP sockets. |
| DNS | Learn hostname resolution. |
| API Reference | See the public API surface. |
Next step
Continue with UDP.