본문 바로가기

BackEnd/C랑 C++

Boost-echo tcp client & server를 살펴보자 (1)

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