Boost는 C++을 하다보면 언젠간 반드시 꼭 사용하게될 라이브러리이다
Boost에 대한 설명은 아래에 첨부할 Boost 공식홈페이지에서 한번 읽어보는걸 추천한다 (재밌음)
처음에 boost를 사용할 때는,
STL(Standard Template Library)이랑 역할이 같은 것 같은데, 왜 쓰지? 했는데
STL을 활용해서 뭔가 좀더 해야할 때, boost에는 이미 있는 기능이 많이 있었다
예제 활용해서 쓰다보면 금방 익숙해질 수 있당
서론이 길었는데,
Boost 활용을 좀 더 잘해보기 위해서, 라이브러리 샘플 코드를 한번씩
공부해보려고 한다
실제 활용할 때 놓친 부분 확인도 할겸ㅋ
Boost Asio에 대한 설명은 링크를 참고하길 바란다
첫 번째로 확인한 예제는 Async TCP Server와 Client 구현 예제이다
boost 코드는 첨부 링크에도 달아 두었으니 참고하시길...
샘플코드에 동작을 확인할 로그만 몇 줄 추가해서 확인한건 깃헙에 올려두었당..
(github.com/bell-2/practice_01_boost)
내가 이해하는데 시간 걸린 부분만 정리하는걸로
1. 어떤 Boost 예제를 볼까
- Async TCP Server (/boost_asio/example/echo/async_tcp_echo_server.cpp)
- Async TCP Client(/boost_asio/example/timeouts/async_tcp_client.cpp)
Timouts 하위에 있는 코드를 사용하려 했는데,
Server용 코드는 echo에 포함되어있는게 더 보기 편해서,
요렇게 두 개를 알아봤다
sync는 async 이해하면 쉽게 이해감
2. Async TCP Server
(1) Asio Service 객체 생성
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
main 함수를 먼저 보면,
Boost Asio를 사용할 수 있도록 하는 io_service 객체를 생성하고
server라는 클래스에 해당 객체를 넣어 생성자를 호출하고 있다
server 클래스에서 io_service에 뭔가를 셋팅 해주려고 넣는걸테고,
그렇게 설정된 io_service에 대해서 run()을 해주고 있다
io_service 시작 → run()
io_service 종료 → stop()
그럼 server 클래스에서 io_service에게 뭘 했는지 보도록 하자
(2) Accept 하는 server Class
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session* new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
/* .... */
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
server 클래쓰의 생성자부터 보면,
io_service 객체를 받아오고, 서버가 열어둘 port 정보는
tcp::acceptor 로 주고 있는 것을 볼 수 있다 (io_service도)
ip::tcp:acceptor
원래 이름은 typedef basic_socket_acceptor <tcp> acceptor;이다
(socket 통신에서 사용하는 그 acceptor 맞습니다)
클라이언트가 접속할 Port를 열어두고 연결에 대해 accept할 준비를 한다
boost 공홈에 type과 member functions들이 정의 되어있으니 참고한다
basic_socket_acceptor::async_accept
비동기 통신의 accept라 이름이 async_accept다
말 그대로 async accept를 시작하는 역할이다
함수 모양은 다음과 같다
template< typename MoveAcceptHandler>
DEDUCED async_accept(
boost::asio::io_context & io_context, MoveAcceptHandler && handler);
주목해야할건 두 번째 인자인 handler이다
boost는 asio 통신의 성공/실패의 결과를 callback 형태로 둘려준다
그래서 accept_async()를 수행한거에도 asio로부터 결과를 수신할
핸들러를 등록을 해주는거다
위의 예제에서는 boost::bind()를 사용해서 등록할 핸들러와
파라미터를 같이 넘겨주고 있다
그러면 callback 함수로 등록해주고 있는 server::handle_accept를 봐보자
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
cout << "START SESSION..." << endl;
new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
{
delete new_session;
}
}
asio에서 accept를 하고나면 우리가 등록한 이 함수가 실행이 된다
성공/실패에 대한 결과는 boost::system::error_code 형태로 받게 된다
boost::system::error_code
error code의 리스트는 enum 으로 정의되어 있다
값이 뭔지 얻어오고 싶다면 아래 두개 api로 가져올 수 있다
error.value() → 숫자 형태의 Error code
error.message() → 문자열로 받을 수 있음
다시 코드로 돌아가서,
예제에서는 session 클래쓰의 객체를 받고,
여러 클라이언트로부터 연결 요청이 올 것을 위해
새로운 accept를 생성해주는 걸 볼 수 있다
(3) session 생성
그러면 서버에서 클라이언트와의 세션이 생성되고
서버가 클라이언트의 요청을 읽고 그에 대해 응답하는
session 클래스를 보도록 하겠다
session* new_session = new session(io_service_);
async_accept에서도 파라미터로 넣어주고 있는 new_session 객체
class session
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
cout << "NEW SESSION..." << endl;
}
/*....*/
private:
tcp::socket socket_;
};
session 클래스의 생성자에서는 io_service 객체를 받아서
tcp::socket를 초기화 해주고 있다
ip::tcp::socket
원래 이름은 typedef basic_stream_socket< tcp >이다
stream-oriented socket 기능을 제공한다고 한다
말이 어렵지만, socket 통신을 가능하도록 해주는 변수일뿐...!
fd를 통해서 하는 것처럼...!
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
/*...*/
new_session->start();
/*...*/
}
handle_accept에서 session 클래스 객체에 대해 start()라는
함수를 호출하는게 보였을거다
void start()
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
얘는 뭐하는 함수냐면, 아까 위에서 accept를 하고 싶으면
accept 결과를 수신할 핸들러를 등록하라고 했었다
마찬가지로,
서버가 클라이언트의 요청을 읽기 위해 read를 하고 싶으면
read의 결과를 받을 핸들러(session::handle_read)를
등록해서 사용하란거다
boost::asio::async_read_some
비동기식 read를 시작하도록 하는 역할을 한다
찾아보면, read하는 함수는 async_read(), async_read_untill() 등등
여러가지가 있었음
async_read_some 원형은 다음과 같다
template<
typename MutableBufferSequence,
typename ReadHandler>
void async_read_some(
const MutableBufferSequence & buffers,
ReadHandler handler);
뭐야 뭔데? 할 수 있잠,ㄴ
단순히 (읽은거 저장할 buffer, 등록할 핸들러) 이거다
boost::asio::buffer
읽은 데이터를 담을 수 있는 버퍼이다
필요하면 복사할 수도 있는데, 메모리 소유권은 호출자에 있다고 하니
유효한 버퍼인지 잘 보도록 하자
bytes_transferred
size_t 로 정의되어 있고, asio가 read한 byte 수를
핸들러를 통해 같이 알려준다
(4) 클라이언트 요청을 읽자
그러면 async_read_some에 핸들러로 등록한
read한 결과를 돌려 받을 수 있는 handle_read()를 보도록 하자
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
cout << "RECV MSG:\n" << data_ << endl;
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
나는 예제 보면서 헷갈렸던게
아니 왜 read를 handle하는 함수인데
여기서 write를 하는 것 같은 함수가 있는거지...?? 였다
근데 이해를 하고 나니까... 다 읽었으면
서버니까 클라이언트에게 응답 해줘야 당연한거였다
여기서도 단순히 error 체크만 해주고,
응답을 보낼 async_write() 함수를 호출해주고 있담
근데 해당 예제는 클라이언트에게 받은 메시지를
echo 형태로 서버가 그대로 다시 돌려주는 예제라서
bytes_transferred와 받은 buffer의 data를 그대로 다시 보내주고 있으니까
실제 사용 시에는 다른 변수를 써서 해야한다
boost::asio::async_write
async_read()와 비슷하게 얘도 write 기능을 수행할 수 있는
함수를 여러개 제공한다
(async_write_at, async_write_some 등)
얘도 동일하게
boost :: asio :: async_write (소켓, boost :: asio :: buffer (데이터, 크기), 핸들러);
이렇게 써주면 되겠다
(5) 클라이언트에게 응답을 보내자
마지막으로 응답 보내는 코드를 보겠다
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
소름돋게 다른 함수랑도 비슷하게
error 체크 하고, 잘 응답을 보냈다면 그 다음 뭐할지를 작성하였다
이 예제는 에코 서버라 계속 메시지를 보내고 받고 하는거라
다시 read를 하러 가고 있지만
일반적인 서버를 구현한다면, 연결을 끊고 세션정리를 한다거나
그러면 될 것으로 보인다
클라이언트는 언제 정리할까? 조금 귀찮아진다...
근데 이미 세션정리하다가 코어 몇십개 내고 왔더니
정리하는게 의미가 있다는 생각이 든다 ^^
아 이게 이래서 이거였구나...를 알 수 있었다
아 뿌.듯.하.다
혹시 잘못된 부분이 있다거나 그러면 알려주십쇼
😄참고 자료😄
www.boost.org/
Boost C++ Libraries
Welcome to Boost.org! Boost provides free peer-reviewed portable C++ source libraries. We emphasize libraries that work well with the C++ Standard Library. Boost libraries are intended to be widely useful, and usable across a broad spectrum of applications
www.boost.org
www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/example/timeouts/async_tcp_client.cpp
doc/html/boost_asio/example/timeouts/async_tcp_client.cpp - 1.45.0
www.boost.org
www.boost.org/doc/libs/1_52_0/doc/html/boost_asio/example/echo/async_tcp_echo_server.cpp
doc/html/boost_asio/example/echo/async_tcp_echo_server.cpp - 1.52.0
www.boost.org
www.boost.org/doc/libs/1_76_0/doc/html/boost_asio.html
Boost.Asio - 1.76.0
Copyright © 2003-2021 Christopher M. Kohlhoff Boost.Asio is a cross-platform C++ library for network and low-level I/O programming that provides developers with a consistent asynchronous model using a modern C++ approach. Overview An overview of the featu
www.boost.org
'BackEnd > C랑 C++' 카테고리의 다른 글
C++ 스마트 포인터를 알아보자 - (2) shared_ptr (0) | 2021.06.25 |
---|---|
Error 해결: undefined reference to `vtable for XXX' (0) | 2021.06.24 |
C++ 스마트 포인터를 알아보자 - (1) unique_ptr (0) | 2021.05.25 |
Boost Asio에 대해서 알아보자 (6) | 2021.05.20 |
C++ 클래스 멤버변수 초기화를 알아보자 (4) | 2021.03.25 |