- 무엇? 네트워킹/타이머/직렬포트/파일(플랫폼 일부) 등 I/O를 비동기로 다루는 크로스플랫폼 라이브러리.
- 핵심 모델: 이벤트 루프(
io_context) + 실행자(Executor) + 비동기 연산(CompletionToken = 콜백/std::future/코루틴). - 백엔드: epoll/kqueue/IOCP 등 OS 고성능 I/O API 위에서 동작(사용자는 신경 안 써도 됨).
- 두 가지 페이스: 동기 API(간단하지만 블로킹) vs 비동기 API(확장성과 성능).
기본 구성 요소
io_context: 모든 비동기 작업의 “심장”.io.run()이 이벤트 루프를 돌립니다.- 소켓/타이머/리졸버:
ip::tcp::socket,steady_timer,ip::tcp::resolver… - 버퍼:
buffer(ptr, n),dynamic_buffer(string)등. - 동기/비동기 API:
- 동기:
read,write,connect(예외 orerror_code반환) - 비동기:
async_*(콜백/코루틴 등 CompletionToken으로 완료 통지)
- 동기:
- 동시성 제어:
strand(같은 strand에 바인딩된 핸들러는 절대 병렬 실행 안 됨) - 취소/타임아웃: 타이머 +
cancel()조합, 혹은 코루틴 + 선택적 취소.
0) 설치 & 빌드
- 헤더 포함:
#include <boost/asio.hpp> - 링크: 최신 Boost는 대개 헤더온리지만, 환경에 따라
-lboost_system링크가 필요할 수 있음. - g++ 예시:
g++ -std=c++20 -O2 main.cpp -lboost_system -lpthread
1) “헬로, 비동기” — 타이머 예제 (콜백)
#include <boost/asio.hpp>
#include <chrono>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
int main() {
asio::io_context io;
asio::steady_timer timer(io, 1s);
timer.async_wait([](const boost::system::error_code& ec) {
if (!ec) std::cout << "1초 후!\n";
});
io.run();
}
C++20 코루틴 버전
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <chrono>
#include <iostream>
namespace asio = boost::asio;
using asio::awaitable;
using asio::co_spawn;
using asio::detached;
using asio::use_awaitable;
using namespace std::chrono_literals;
awaitable<void> demo_timer() {
asio::steady_timer t(co_await asio::this_coro::executor, 1s);
co_await t.async_wait(use_awaitable);
std::cout << "코루틴: 1초 후!\n";
co_return;
}
int main() {
asio::io_context io;
co_spawn(io, demo_timer(), detached);
io.run();
}
2) TCP 에코 서버 (C++20 코루틴, 깔끔한 최신식)
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/this_coro.hpp>
#include <array>
namespace asio = boost::asio;
using asio::ip::tcp;
using asio::awaitable;
using asio::co_spawn;
using asio::detached;
using asio::use_awaitable;
awaitable<void> session(tcp::socket sock) {
try {
std::array<char, 1024> buf;
for (;;) {
std::size_t n = co_await sock.async_read_some(asio::buffer(buf), use_awaitable);
co_await asio::async_write(sock, asio::buffer(buf.data(), n), use_awaitable);
}
} catch (...) {
// 연결 종료/에러시 세션 종료
}
co_return;
}
awaitable<void> listener(unsigned short port) {
auto ex = co_await asio::this_coro::executor;
tcp::acceptor acc(ex, {tcp::v4(), port});
for (;;) {
tcp::socket sock = co_await acc.async_accept(use_awaitable);
co_spawn(ex, session(std::move(sock)), detached);
}
}
int main() {
asio::io_context io;
// Ctrl+C 안전 종료
asio::signal_set signals(io, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ io.stop(); });
co_spawn(io, listener(5555), detached);
io.run();
}
- 포트
5555로 접속하면 받은 바이트를 그대로 돌려줍니다. - 동시 접속도 코루틴으로 자연스럽게 스케일됩니다.
3) 스레드/동시성 모델
- 단일 스레드:
io.run()을 한 스레드에서만 돌리면, 모든 핸들러가 순차 실행 → 디버깅 쉬움. - 멀티스레드:
std::thread여러 개로io.run()병렬 호출 → 높은 처리량. - strand: 멀티스레드 환경에서 특정 시퀀스를 직렬화하고 싶다면
auto ex = asio::make_strand(io);로 실행자 생성 후 그 실행자에 바인딩하여async_...호출.
예)tcp::socket sock(ex);로 만들면, 그 소켓 핸들러들은 같은 strand에서 순서 보장.
4) 타임아웃/취소 패턴(정석)
비동기 read에 타임아웃 붙이는 대표 패턴:
- 타이머와 read를 동시에 시작
- 먼저 끝난 쪽이 다른 쪽을
cancel()
(코루틴 예시 – 간단 버전)
#include <boost/asio.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/this_coro.hpp>
#include <chrono>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using asio::awaitable; using asio::use_awaitable;
awaitable<std::size_t> read_with_timeout(asio::ip::tcp::socket& s,
asio::mutable_buffer buf,
std::chrono::milliseconds timeout) {
auto ex = co_await asio::this_coro::executor;
asio::steady_timer timer(ex);
boost::system::error_code ec_read, ec_timer;
std::size_t n_read = 0;
timer.expires_after(timeout);
bool read_done = false, timer_done = false;
s.async_read_some(buf, [&](auto ec, std::size_t n){
ec_read = ec; n_read = n; read_done = true; timer.cancel();
});
timer.async_wait([&](auto ec){ ec_timer = ec; timer_done = true; s.cancel(); });
// 이벤트 루프에 제어를 돌려주며 두 작업 중 하나가 먼저 끝나길 기다림
while (!read_done && !timer_done)
co_await asio::post(ex, use_awaitable);
if (timer_done && ec_read == asio::error::operation_aborted)
throw std::runtime_error("read timeout");
if (ec_read) throw boost::system::system_error(ec_read);
co_return n_read;
}
5) CompletionToken 패턴(Asio의 큰 장점)
하나의 async_*가 다양한 “완료 통지 방식”을 지원:
- 콜백:
async_read(..., [](error_code, size_t){}) - 코루틴:
co_await async_read(..., use_awaitable) std::future:async_read(..., use_future)
→ 프로젝트 특성에 맞게 스타일을 고를 수 있고, 나중에 바꾸기도 쉽습니다.
6) 버퍼 & 스트림 퀵 팁
- 고정 버퍼:
asio::buffer(ptr, size) - 가변 버퍼(문자열 확대/축소):
asio::dynamic_buffer(std::string&) - 라인/구분자 파싱:
async_read_until(sock, dynamic_buffer(buf), "\r\n")
간단 HTTP GET(동기, 이해용) 예:
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
int main(){
try {
asio::io_context io;
tcp::resolver r(io);
auto eps = r.resolve("example.com", "80");
tcp::socket s(io);
asio::connect(s, eps);
std::string req =
"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
asio::write(s, asio::buffer(req));
std::string buf;
asio::streambuf sb; // or use dynamic_buffer(buf)
boost::system::error_code ec;
while (asio::read(s, sb, ec)) {}
if (ec != asio::error::eof && ec) throw boost::system::system_error(ec);
std::cout << &sb;
} catch (std::exception& e) {
std::cerr << "err: " << e.what() << "\n";
}
}
7) 실전 베스트 프랙티스
- 수명 관리: 비동기 세션 객체는
std::shared_ptr로 소유해 핸들러에서 안전하게 참조. - strand 적극 사용: 멀티스레드에서 소켓 1개당 1-strand로 레이스 컨디션 방지.
- 타임아웃 기본 탑재: 네트워크는 언제든 멈춥니다. 타이머+
cancel()패턴을 습관화. - 소켓 옵션:
tcp::no_delay(true)(Nagle off),reuse_address(true)등 상황에 맞게. - 작은 핸들러: 핸들러 안에서 무거운 연산 금지(필요시
post()로 분리). - 에러코드 우선: 예외보다
error_code로 제어 흐름 명확화(핵심 루프에서 try/catch 남발 X). - 구성요소 분리: acceptor, session, router/codec 등 역할별 클래스로 분리하면 테스트/유지보수 쉬움.
8) 흔한 함정
io_context.run()을 호출 안 함(혹은 일찍 반환됨) → 콜백/코루틴이 절대 실행 안 됨.
필요시auto guard = asio::make_work_guard(io);로 이벤트 루프가 일찍 끝나지 않게 유지.- 같은 소켓에 동시에 두 개의
async_read날리기 → 정의되지 않은 동작. 읽기/쓰기 각각 1개씩만 동시 진행. - strand 없이 멀티스레드로 같은 자원 접근 → 미묘한 레이스.
make_strand(io)사용! - 타임아웃 미적용 → “유령 커넥션”이 리소스 잡아먹음.
9) Boost.Asio vs standalone Asio
- 네임스페이스:
boost::asiovsasio - API는 거의 동일. Boost 생태계(seralization, beast 등)와 어울리면
Boost.Asio가 편합니다.
10) 작은 치트시트
- 이벤트 루프 시작:
io_context io; io.run(); - 멀티스레드:
N개 스레드에서io.run()병렬 호출 - strand:
auto ex = asio::make_strand(io); - 코루틴 토큰:
use_awaitable - 스폰:
co_spawn(io, task(), detached); - 타임아웃:
steady_timer+cancel()조합 - 종료:
signal_set(SIGINT,SIGTERM).async_wait(... io.stop());
답글 남기기