C++ Network Programming with Asio
Blocking sockets vs the asio proactor, TCP echo server, asynchronous read/write, timers, and production settings.
Blocking Sockets: the Foundation
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
// 服务端:socket → bind → listen → accept
int srv = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
int one = 1;
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
bind(srv, (sockaddr*)&addr, sizeof(addr));
listen(srv, 64); // backlog:等待队列
int conn = accept(srv, nullptr, nullptr); // 阻塞直至连接到达
char buf[4096];
ssize_t n = read(conn, buf, sizeof(buf)); // 阻塞
write(conn, buf, n);
close(conn);
One blocking socket is simple; one thread per connection does not scale past a few thousand. The reactive alternative is epoll/kqueue, and asio gives you that power with a friendlier API.
Asio: the Proactor Model
#include <asio.hpp>
using asio::ip::tcp;
// 异步 TCP 回显:io_context 驱动事件循环
std::make_shared<Session>(io).start();
io.run();
class Session : public std::enable_shared_from_this<Session> {
public:
Session(asio::io_context& io) : socket_(io) {}
tcp::socket& socket() { return socket_; }
void start() { do_read(); }
void do_read() {
auto self = shared_from_this(); // 延长生命周期
socket_.async_read_some(asio::buffer(data_),
[self](auto ec, std::size_t n) {
if (!ec) self->do_write(n); // 回写
});
}
void do_write(std::size_t len) {
auto self = shared_from_this();
asio::async_write(socket_, asio::buffer(data_, len),
[self](auto ec, std::size_t) {
if (!ec) self->do_read(); // 继续读
});
}
private:
tcp::socket socket_;
std::array<char, 4096> data_{};
};
The completion handler captures self (shared_from_this) so the Session outlives the async operation; otherwise the callback could touch a deleted object. This is the single most important asio idiom.
Timers and io_context Threads
// 定时器:心跳 / 超时清理
asio::steady_timer timer(io, std::chrono::seconds(30));
timer.async_wait([](auto ec) {
if (!ec) sweep_expired(); // ec == operation_aborted 表示被取消
});
// 多线程:一个 io_context,N 个线程 run()
std::vector<std::thread> pool;
for (int i = 0; i < 4; ++i)
pool.emplace_back([&io] { io.run(); });
// 回调可能在任一线程执行
// 需要 strand 保证处理顺序
asio::strand<asio::io_context::executor_type> strand(io.get_executor());
post(strand, [] { /* 串行化执行 */ });
Production Settings
- Set SO_REUSEADDR (avoid TIME_WAIT bind failures), TCP_NODELAY for small interactive writes.
- Size buffers to realistic payloads; copy payload out of the IO buffer before the next read.
- Log ec values: operation_aborted is normal on shutdown, eof means peer closed.
- Consider coroutines (asio co_await, C++20) to flatten callback chains.
阻塞 socket:基础
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
// 服务端:socket → bind → listen → accept
int srv = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
int one = 1;
setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
bind(srv, (sockaddr*)&addr, sizeof(addr));
listen(srv, 64); // backlog:等待队列
int conn = accept(srv, nullptr, nullptr); // 阻塞直至连接到达
char buf[4096];
ssize_t n = read(conn, buf, sizeof(buf)); // 阻塞
write(conn, buf, n);
close(conn);
单个阻塞 socket 很简单;每连接一线程过不了几千并发。反应式路线是 epoll/kqueue,而 asio 用更友好的 API 提供同样的能力。
Asio:Proactor 模型
#include <asio.hpp>
using asio::ip::tcp;
// 异步 TCP 回显:io_context 驱动事件循环
std::make_shared<Session>(io).start();
io.run();
class Session : public std::enable_shared_from_this<Session> {
public:
Session(asio::io_context& io) : socket_(io) {}
tcp::socket& socket() { return socket_; }
void start() { do_read(); }
void do_read() {
auto self = shared_from_this(); // 延长生命周期
socket_.async_read_some(asio::buffer(data_),
[self](auto ec, std::size_t n) {
if (!ec) self->do_write(n); // 回写
});
}
void do_write(std::size_t len) {
auto self = shared_from_this();
asio::async_write(socket_, asio::buffer(data_, len),
[self](auto ec, std::size_t) {
if (!ec) self->do_read(); // 继续读
});
}
private:
tcp::socket socket_;
std::array<char, 4096> data_{};
};
完成回调必须捕获 self(shared_from_this),让 Session 活到异步操作结束,否则回调可能访问已删除对象——这是 asio 最重要的惯用法。
定时器与多线程 io_context
// 定时器:心跳 / 超时清理
asio::steady_timer timer(io, std::chrono::seconds(30));
timer.async_wait([](auto ec) {
if (!ec) sweep_expired(); // ec == operation_aborted 表示被取消
});
// 多线程:一个 io_context,N 个线程 run()
std::vector<std::thread> pool;
for (int i = 0; i < 4; ++i)
pool.emplace_back([&io] { io.run(); });
// 回调可能在任一线程执行
// 需要 strand 保证处理顺序
asio::strand<asio::io_context::executor_type> strand(io.get_executor());
post(strand, [] { /* 串行化执行 */ });
生产实践
- 设 SO_REUSEADDR(避免 TIME_WAIT 占端口)、小包交互场景设 TCP_NODELAY。
- 缓冲区按真实报文大小设;下一次读之前把数据拷出 IO 缓冲。
- 记录 ec:operation_aborted 在关闭时正常,eof 表示对端关闭。
- 可用协程(asio co_await,C++20)把回调链拉平。