Download - [KGC 2012]Boost.asio를 이용한 네트웍 프로그래밍
Boost.Asio를 이용한 네트웍 프로그래밍
최흥배
Twitter : @jacking75
Visual C++ MVP 2008 ~ 2011
https://twitter.com/jacking75
http://jacking.tistory.com/
Boost.Asio ?
Boost.Asio란?
• Boost 라이브러리의 일부. 주로 네트웍 프로그래밍에 사용
• Asynchronous I/O (비동기 입출력).
• I/O와 같이 시간이 걸리는 처리를 OS의 비동기 기능과 스레드를 사용하여 처리.
• 파일 입출력이나 시리얼 입출력, 또는 일반적인 비동기 프로그래밍에서도 사용.
• 멀티 플랫폼 지원.
OS 플랫폼 별 구현
• Linux Kernel 2.4 select를 사용하므로 FD_SIZE 크기를 넘지 못함
• Linux Kerner 2.6 epoll을 사용
• FreeBSD, Mac OS X Kqueue를 사용
• Solaris /dev/poll을 사용
• Windows(Windows 2000 이후) Overlapped I/O와 I/O Completion을 사용
Boost.Asio의 주요 클래스
• boost::asio::io_service 가장 중요
• ip::tcp::socket (http에도 사용)
• ip::udp::socket
• ip::icmp::socket (ping 등에 사용)
• ssl::context (Open SSL이 필요)
• serial_port
• boost::deadline_timer
믿을 수 있나?
• 신뢰성이 높음.
• (아마도)차기 C++ 표준에 들어갈 확률이 높음...
• 한국의 몇몇 온라인 게임에서 이미 사용 중, 또 한국의 모 대형 IT 회사의 내부 네트웍 라이브러리로 Boost.Asio를 사용하고 있다고 함 .
성능?
편의성?
직접 만들기….
Boost 빌드?
혹은 직접 빌드 하기…..
Demo !!!
Demo : Asio를 동기 모드로 사용
Tutotial_001
int main() { asio::io_service io_service; tcp::socket socket(io_service); boost::system::error_code error; auto endpoint = tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 31400); socket.connect( endpoint, error ); if (error) { std::cout << "connect 실패 : " << error.message() << std::endl; } else { std::cout << "connected" << std::endl; } return 0; }
Demo : Asio를 비동기 모드로 사용
Tutotial_002
void connect() { auto endpoint = tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 31400); socket_.async_connect( endpoint, boost::bind( &Client::on_connect, this, asio::placeholders::error) ); } void on_connect(const boost::system::error_code& error) { if (error) { std::cout << "connect failed : " << error.message() << std::endl; } else { std::cout << "connected" << std::endl; } }
int main() { asio::io_service io_service; Client client(io_service); client.connect(); io_service.run(); }
Boost application performance using asynchronous I/O http://www.ibm.com/developerworks/linux/library/l-async/
Typical flow of the synchronous blocking I/O model
Typical flow of the synchronous non-blocking I/O model
Typical flow of the asynchronous blocking I/O model (select)
Typical flow of the asynchronous non-blocking I/O model
Proactor and Boost.Asio
Boost.Asio와 IOCP
asio::io_service io_service; tcp::socket socket(io_service); io_service.run();
g_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); client = accept(server, (struct sockaddr*)&addr, &addrlen); g_hIocp = CreateIoCompletionPort((HANDLE)client, g_hIocp, (DWORD)con, 0); GetQueuedCompletionStatus(g_hIocp, &readbytes, &dwCompKey, (LPOVERLAPPED *)&pOverlap, INFINITE);
Asio의 비동기 모델 - 스레드 모델
애플리케이션
Boost 소켓
io_service
OS
callback 함수 오브젝트
I/O
run()
참조 : http://d.hatena.ne.jp/Softgels/20090304/1236123151
int main() { asio::io_service io_service; Client client(io_service); client.connect(); io_service.run(); }
Asio의 비동기 모델 - 멀티 스레드 모델
Boost 소켓
io_service
OS
callback 함수 오브젝트
I/O
run()
참조 : http://d.hatena.ne.jp/Softgels/20090304/1236123151
Worker 스레드 Worker 스레드
Worker 스레드
Boss 스레드
만들고, 종료까지 기다림
for( INT32 i = 0; i < 3; ++i ) { m_IoWorkThreadList.emplace_back ( std::thread( boost::bind( &boost::asio::io_service::run, &m_IOService) ) ); }
소켓, 타이머 등 비동기 입출력 이벤트를 디스패치하는 클래스
멤버 함수
size_t run(); size_t run( boost::system::error_code& e ); size_t run_one(); size_t run_one( boost::system::error_code& e ); size_t pool(); size_t pool( boost::system::error_code& e ); size_t pool_one(); size_t pool_one( boost::system::error_code& e );
run 함수는 모든 이벤트가 처리될 때까지 블럭된다.
run_one 함수는 하나의 이벤트가 처리될 때까지 블럭된다.
poll 함수는 블럭하지 않으면서 처리할 이벤트를 모두 처리하고 종료한다.
poll_one 함수는 블럭하지 않으면서 1개((또는 0개) 이벤트를 처리하고 종료한다.
모두 실행한 이벤트 개수를 반환한다.
io_service 클래스
정지 함수 void stop(); void reset();
run이나 poll 함수의 처리 루프를 정지할 때 stop 함수를 사용한다.
stop 사용 후에 다시 run이나 poll을 사용하면 에러가 발생. 이때는 reset 함수를 사용해야 한다.
io_service는 Asio의 시작이자 끝!!!
TCP/IP boost::asio::ip::tcp
UDP/IP
boost::asio::ip::udp
protocol
v4(), v6()
endpoint 접속 주소를 지정 INADDR_ANY로 포트번호만 지정하던가 IP와 포트번호를 지정할 수 있다.
resolver 호스트 이름에서 IP 주소로 변환시키는 클래스.
acceptor 클라이언트로부터 TCP 접속을 받아 들이기 위해서 서버에서 사용하는 클래스. accpet 처리에는 동기와 비동기 두 개의 버전이 있다.
TCP/UDP 통신
고정 사이즈 버퍼 배열, boost::array, std::vector, std::string에 buffer 함수를 적용하며 기본 입출력에 넘길 수 있는 버퍼 오브젝트가 된다. size_t 인수로 사이즈를 지정하면 모두 바이트 단위로 지정한다.
함수 버퍼
const_buffer 또는 mutable_buffer를 저장한 컨테이너는 기본 입출력 함수에 넘길 수 있다. read 계열 함수에 넘긴 경우 모든 버퍼의 내용을 연결된 것으로 저장된다.
스트림 버퍼 STL의 스트림 버퍼로서 사용할 수 있으며 기본 입출력 버퍼이다.
buffer 클래스
동기 읽기 완료 조건을 만족하던가 에러가 발생할 때까지 블럭. read_until에서는 구분 문자 delim을 읽을 때까지 블럭한다. ErrorHandler는 아래의 시그네쳐를 가진 함수 오브젝트를 지정할 수 있다. void error_han( const boost::system::error_code& e );
비동기 읽기 블럭킹 하지 않고 버퍼에 데이터를 읽어 들인다. 완료하던가 에러가 발생하면 소켓에 연결된 io_service의 run 함수에서 Handler 함수 오브젝트를 호출한다. Handler에는 아래의 시그네처를 가진 함수 오브젝트를 지정할 수 있다. void condition( const boost::system::error_code& e, size_t bytes_transferred );
동기 쓰기 버퍼로 부터 데이터를 쓴다. 완료 또는 에러가 발생할 때까지 블럭킹 한다.
비동기 쓰기 블럭킹 하지 않고 버퍼에서 데이터를 쓴다. 완료 또는 에러가 발생하면 소켓에 연결된 io_service의 run 함수로부터 Handler 함수 오브젝트가 호출된다. Handler에는 아래의 시그네처를 가진 함수 오브젝트를 지정할 수 있다. void condition( const boost::system::error_code& e, size_t bytes_transferred );
기본 입출력
시스템 의존 에러코드를 랩핑한 클래스.
bool 타입으로 평가하면 에러 때는 true, 정상일 때는 false.
message 및 wmessage 멤버 함수를 사용하면 상세한 에러를 문자열로 얻을 수 있다.
error_code 클래스
class error_code { public: error_code(); error_code( value_type val, error_category cat ); operator unspecified_bool_type() const; bool operator() const; value_type value() const; error_category category() const; int to_error() const; std::string message() const; wstring_t wmessage() const; };
Demo : 간단한 채팅
http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/examples.html#boost_asio.examples.allocation Tutotial_Chat
Demo : io_service를 포함하는 설계
Tutorial_IOServicePattern
Demo : 간단한 백그라운드 처리
Tutorial_SimpleBackground
Demo : poll
Tutorial_poll
run()은 이벤트 큐가 없을 때까지 대기,
poll()은 준비가 끝난 이벤트를 실행.
준비가 끝난 이벤트란 핸들러를 실행할 수 있는 준비가 끝난 것을 말한다. 그러므로 타이머나 IO 완료 통지는 poll()로는 기다릴 수 없다.
poll()에서 대기하지 않은 핸들러는 poll()이 끝난 후에 run()이나 run_one()등으로 처리하면 된다.
Demo : 워커 스레드 패턴
Tutorial_WorkerThreadPattern
Demo : 이벤트 디스패치 스레드
Tutorial_EventDispatch
Io_service::work
비동기 처리를 등록하기 전에 io_servive::run()을 호출하면 io 작업이 완료되어 버리고 스레드는 종료된다. 이것을 방지하기 위해 io_service::work를 사용한다. Io_service::work를 사용하면 work가 파괴될 때까지 ioLservice::run()은 종료하지 않는다
boost::shared_ptr<boost::asio::io_service::work> work_;
work_.reset(new boost::asio::io_service::work(io_service_));
class strand
{ public: strand( io_service& io ); template< typename Handler > unspecified wrap( Handler han ); };
핸들러 함수 han을 랩핑해서 동일의 strand로 랩핑된 핸들러가 복수의 스레드에서 병렬로 실행되지 않도록 동기화된 핸들러 함수로 바꾸어준다.
strandobj.wrap(han)의 결과는 그대로 기본 입출력 함수나 타이머 등의 핸들러 함수로서 사용할 수 있다.
async_op_1(..., s.wrap(a)); async_op_2(..., s.wrap(b));
strand 클래스
Demo : Windows에서 파일을 비동기로 읽기
Tutorial_AsyncFileRead
Demo : boost::asio::deadline_timer의
cancel()
Tutorial_TimerCancel
Demo : boost::asio::deadline_timer의
cancel_one()
Tutorial_TimerCancelOne
Demo : post와 dispatch의 차이
Tutorial_post_dispatch
dispatch()에서 호출한 함수는 dispatch()를 호출한 곳과 같은 스레드에서 호출된다. 그러나 post()의 경우는 다른 스레드에서 호출될 수 있다.
dispatch()는 호출한 곳의 함수가 비동기로 실행되고 있는 경우 비동기가 아닌 직접 함수를 호출하거나, post()와 같이 Queue에 등록하여 비동기로 실행한다.
dispatch()의 경우 핸들러 함수 콜스택이 깊어질 수 있으나(재귀호출을 하면) post()는 호출한 핸들러 함수에서 제어 흐름이 일단 io_service 내의 메시지 루프로 돌아간 후 post 된 핸들러 함수가 호출되므로 콜 스택이 깊어지지 않는다.
dispatch()의 경우 컨텍스트가 바뀌지 않으므로 또 내용이 관련된 핸들러가 연속해서 불려지므로 참조 국소성이 유지되지 않을까 생각한다.
참고 자료
참고
Boost 라이브러리 공식 사이트http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio.html
boostpro 사이트 http://www.boostpro.com/download/
Boost 라이브러리 직접 빌드하기 http://jacking.tistory.com/986
asio C++ Library http://think-async.com/Asio
Boost e-Book http://en.highscore.de/cpp/boost/
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream> namespace asio = boost::asio; namespace ip = asio::ip; class Client { asio::io_service& io_service_; ip::tcp::resolver resolver_; public: Client(asio::io_service& io_service) : io_service_(io_service), resolver_(io_service) { ip::tcp::resolver::query query("google.com", "http"); resolver_.async_resolve(query, boost::bind( &Client::on_resolve, this, asio::placeholders::error, asio::placeholders::iterator) ); }
resolver를 사용한 도메인 네임 to IP 어드레스
private: void on_resolve(const boost::system::error_code& error, ip::tcp::resolver::iterator endpoint_iterator) { if (error) { std::cout << error.message() << std::endl; } else { ip::tcp::resolver::iterator end; for (; endpoint_iterator != end; ++endpoint_iterator) { std::cout << endpoint_iterator->endpoint().address().to_string() << std::endl; } } } }; int main() { asio::io_service io_service; Client client(io_service); io_service.run(); }
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio; int main() { const std::string host_name = asio::ip::host_name(); std::cout << "hostname: " << host_name << std::endl; }
자신의 Host 이름 얻기
비동기 핸들러 디버그 지원
1.47 버전부터 지원하는 것으로 BOOST_ASIO_ENABLE_HANDLER_TRACKING 라는 비동기 핸들러 디버그 지원 매크로를 사용하면 consol에 로그가 찍힌다. #define BOOST_ASIO_ENABLE_HANDLER_TRACKING
@asio|1310697299.008735|0*1|[email protected]_send
@asio|1310697299.008735|0*2|[email protected]_receive
@asio|1310697299.008735|>1|ec=system:0,bytes_transferred=8
request : request @asio|1310697299.008735|<1| @asio|1310697299.039985|>2|ec=system:0,bytes_transferred=9
response : response
@asio|1310697299.039985|<2| @asio|1310697300.039985|0*3|[email protected]_send
@asio|1310697300.039985|0*4|[email protected]_receive
@asio|1310697300.039985|>3|ec=system:0,bytes_transferred=8
request : request @asio|1310697300.039985|<3| @asio|1310697300.039985|>4|ec=system:0,bytes_transferred=9
response : response
@asio|1310697300.039985|<4| @asio|1310697301.039985|0*5|[email protected]_send
@asio|1310697301.039985|0*6|[email protected]_receive
@asio|1310697301.039985|>5|ec=system:0,bytes_transferred=8
request : request ...
출력 양식 <tag>|<timestamp>|<action>|<description> <tag>는 보통 @asio로 된다. 이 행은 트랙킹 메시지라는 것을 알려준다. <timestamp>는 유닉스 시간이다. 1970년 1월1일부터의 시간. 초와 밀리 초가 출력된다. <action> 규칙은 아래와 같음
>n : 프로그램 핸들러 수
<n : 종료한 핸들러 수
!n : 예외로 종료한 핸들러 수
~n : 호출된 적이 없이 파괴된 핸들러 수
n*m : 새롭게 작성한 비동기 조작 수 n과 완료 대기 핸들러 수 m
n : 그 외 조작을 한 핸들러 수. close나 cancel 등. <description>는 동기 or 비동기 조작이 실행되었는가를 출력한다. 형식은 「<object-type>@<pointer>.<operation>」. 핸들러 인수는 콤마로 구분된 리스트로서 출력한다.
완료 조건
transfer_all_t transfer_all(); transfer_at_least_t transfer_at_least( size_t min );
기본 입출력 함수의 CompletionCondition 인수로 넘길 수 있는 오브젝트
예를들면 transfer_all() 함수를 넘기면 버퍼 모두를 사용할 때까지 입출력이 진행된다. transfer_at_least(m)를 넘기면 최저 m 바이트 이상의 쓰기가 진행될 때까지는 입출력이 진행된다. 보통 아래와 같은 시그네쳐를 가진 함수 오브젝트를 넘길 때 사용한다. true를 넘기면 입출력 완료로서 좋다는 것을 표시한다. bool condition( const boost::system::error_code& e, size_t bytes_transferred );
boost::array<char, 128> buf; boost::system::error_code ec; std::size_t n = boost::asio::read( sock, boost::asio::buffer(buf), boost::asio::transfer_all(), ec ); if (ec) { // An error occurred. } else { // n == 128 } boost::array<char, 128> buf; boost::system::error_code ec; std::size_t n = boost::asio::read( sock, boost::asio::buffer(buf), boost::asio::transfer_at_least(64), ec ); if (ec) { // An error occurred. } else
{ // n >= 64 && n <= 128
} boost::asio::async_read( sock, boost::asio::buffer(data, size), boost::asio::transfer_at_least(32), handler );
BSD Socket - Boost.Asio
BSD Socket API Boost.Asio
socket descriptor - int (POSIX) or SOCKET (Windows)
For TCP: ip::tcp::socket, ip::tcp::acceptor For UDP: ip::udp::socket basic_socket, basic_stream_socket, basic_datagram_socket, basic_raw_socket
in_addr, in6_addr ip::address, ip::address_v4, ip::address_v6
sockaddr_in, sockaddr_in6 For TCP: ip::tcp::endpoint For UDP: ip::udp::endpoint ip::basic_endpoint
accept() For TCP: ip::tcp::acceptor::accept() basic_socket_acceptor::accept()
bind() For TCP: ip::tcp::acceptor::bind(), ip::tcp::socket::bind() For UDP: ip::udp::socket::bind() basic_socket::bind()
BSD Socket API Boost.Asio
close() For TCP: ip::tcp::acceptor::close(), ip::tcp::socket::close() For UDP: ip::udp::socket::close() basic_socket::close()
connect() For TCP: ip::tcp::socket::connect() For UDP: ip::udp::socket::connect() basic_socket::connect()
getaddrinfo(), gethostbyaddr(), gethostbyname(), getnameinfo(), getservbyname(), getservbyport()
For TCP: ip::tcp::resolver::resolve(), ip::tcp::resolver::async_resolve() For UDP: ip::udp::resolver::resolve(), ip::udp::resolver::async_resolve() ip::basic_resolver::resolve(), ip::basic_resolver::async_resolve()
gethostname() ip::host_name()
BSD Socket API Boost.Asio
getpeername() For TCP: ip::tcp::socket::remote_endpoint() For UDP: ip::udp::socket::remote_endpoint() basic_socket::remote_endpoint()
getsockname() For TCP: ip::tcp::acceptor::local_endpoint(), ip::tcp::socket::local_endpoint() For UDP: ip::udp::socket::local_endpoint() basic_socket::local_endpoint()
getsockopt() For TCP: ip::tcp::acceptor::get_option(), ip::tcp::socket::get_option() For UDP: ip::udp::socket::get_option() basic_socket::get_option()
inet_addr(), inet_aton(), inet_pton() ip::address::from_string(), ip::address_v4::from_string(), ip_address_v6::from_string()
BSD Socket API Boost.Asio
inet_ntoa(), inet_ntop() ip::address::to_string(), ip::address_v4::to_string(), ip_address_v6::to_string()
ioctl() For TCP: ip::tcp::socket::io_control() For UDP: ip::udp::socket::io_control() basic_socket::io_control()
listen() For TCP: ip::tcp::acceptor::listen() basic_socket_acceptor::listen()
poll(), select(), pselect() io_service::run(), io_service::run_one(), io_service::poll(), io_service::poll_one() Note: in conjunction with asynchronous operations.
BSD Socket API Boost.Asio
readv(), recv(), read() For TCP: ip::tcp::socket::read_some(), ip::tcp::socket::async_read_some(), ip::tcp::socket::receive(), ip::tcp::socket::async_receive() For UDP: ip::udp::socket::receive(), ip::udp::socket::async_receive() basic_stream_socket::read_some(), basic_stream_socket::async_read_some(), basic_stream_socket::receive(), basic_stream_socket::async_receive(), basic_datagram_socket::receive(), basic_datagram_socket::async_receive()
recvfrom() For UDP: ip::udp::socket::receive_from(), ip::udp::socket::async_receive_from() basic_datagram_socket::receive_from(), basic_datagram_socket::async_receive_from()
BSD Socket API Boost.Asio
send(), write(), writev() For TCP: ip::tcp::socket::write_some(), ip::tcp::socket::async_write_some(), ip::tcp::socket::send(), ip::tcp::socket::async_send() For UDP: ip::udp::socket::send(), ip::udp::socket::async_send() basic_stream_socket::write_some(), basic_stream_socket::async_write_some(), basic_stream_socket::send(), basic_stream_socket::async_send(), basic_datagram_socket::send(), basic_datagram_socket::async_send()
sendto() For UDP: ip::udp::socket::send_to(), ip::udp::socket::async_send_to() basic_datagram_socket::send_to(), basic_datagram_socket::async_send_to()
setsockopt() For TCP: ip::tcp::acceptor::set_option(), ip::tcp::socket::set_option() For UDP: ip::udp::socket::set_option() basic_socket::set_option()
BSD Socket API Boost.Asio
shutdown() For TCP: ip::tcp::socket::shutdown() For UDP: ip::udp::socket::shutdown() basic_socket::shutdown()
sockatmark() For TCP: ip::tcp::socket::at_mark() basic_socket::at_mark()
socket() For TCP: ip::tcp::acceptor::open(), ip::tcp::socket::open() For UDP: ip::udp::socket::open() basic_socket::open()
socketpair() local::connect_pair() Note: POSIX operating systems only.