Daytime.3 - An asynchronous TCP daytime server

Programming/Boost asio 2016.04.03 23:09

다음에 기반함

http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/tutorial/tutdaytime3.html



main 함수

int main()
{
  try
  {



먼저, 클라이언트의 연결요청을 받아들일 객체가 필요하다. io_service 객체는 소켓과 같은 I/O 서비스를 제공하므로, 이를 이용하자.


boost::asio::io_service io_service; tcp_server server(io_service);


io_service 객체를 run 하여 비동기 연산을 수행하도록 하자. 


    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}


tcp_server 클래스

class tcp_server
{
public:


생성자에서 포트 13의 요청을 listen 하도록 초기화 해주자.

  tcp_server(boost::asio::io_service& io_service)
    : acceptor_(io_service, tcp::endpoint(tcp::v4(), 13))
  {
    start_accept();
  }

private:


start_accept() 함수에서 소켓을 생성하고 async accept 를 시작하여 새로운 연결을 받아들이자.

  void start_accept()
  {
    tcp_connection::pointer new_connection =
      tcp_connection::create(acceptor_.get_io_service());

    acceptor_.async_accept(new_connection->socket(),
        boost::bind(&tcp_server::handle_accept, this, new_connection,
          boost::asio::placeholders::error));
  }


handle_accept 는 start_accept 함수가 종료되어 accept 연산이 개시될 때 호출된다. 이 함수에서 클라이언트의 요청을 처리하고, start_accept() 를 다시 호출하여 다음 연결 요청을 받을 수 있도록 하자.

  void handle_accept(tcp_connection::pointer new_connection,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_connection->start();
    }

    start_accept();
  }


tcp_connection 클래스


shared_ptr 과 enable_shared_from_this 를 활용하여 관련한 연산이 수행되는 한 tcp_connection 객체가 소멸되지 않도록 유지하자.

class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:
  typedef boost::shared_ptr<tcp_connection> pointer;

  static pointer create(boost::asio::io_service& io_service)
  {
    return pointer(new tcp_connection(io_service));
  }

  tcp::socket& socket()
  {
    return socket_;
  }


start() 함수에서는 boost::asio::async_write() 함수를 활용하여 클라이언트로 데이터를 전송한다.

ip::tcp::socket::async_write_some() 대신 async_write 를 사용하여 전체 데이터 블록이 전송될 수 있도록 했음을 확인하자. 

  void start()
  {



비동기로 메시지를 전송하는 것이 완료될 때까지 데이터가 유효함을 보장하기 위해 클래스 멤버로 데이터를 보관 할 것이다. 

  message_ = make_daytime_string();



boost::bind() 를 활용하여 비동기 연산을 수행할때, 핸들러의 parameters 와 호출부의 arguments 가 일치하도록 주의한다.

placeholder 들 (boost::asio::placeholders::error / boost::asio::placeholders::bytes_transferred) 이 handle_write() 에서 사용되지 않기때문에, 실수로 제거될 수 있다.

    boost::asio::async_write(socket_, boost::asio::buffer(message_),
        boost::bind(&tcp_connection::handle_write, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));


이 연결에 필요한 기타 추가 작업들은 이제 handle_write() 에서 모두 처리하면 된다. 

  }

private:
  tcp_connection(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
  }

  void handle_write(const boost::system::error_code& /*error*/,
      size_t /*bytes_transferred*/)
  {
  }

  tcp::socket socket_;
  std::string message_;
};



필요한 핸들러 인자 제거


우리의 예제에서, error 와 bytes_transsferred 인자는 handle_write 함수 몸체에서 불필요하다. 이러한 경우, 함수에서 이를 제거하여 다음과 같은 시그니쳐를 갖게 할 수 있다.

  void handle_write()
  {
  }


이 함수를 호출부인 boost::asio::async_write() 를 호출할때에는 다음과 같이 수정하면 된다. 

  boost::asio::async_write(socket_, boost::asio::buffer(message_),
      boost::bind(&tcp_connection::handle_write, shared_from_this()));


  

정리

1. main 함수

* tcp_server 에 io_service 를 전달하여 생성하고, tcp_server 클래스를 initiate 한다.

* 메인 스레드에서 tcp_server 에서 사용하는 io_service 의 run() 함수 호출


2. tcp_server 클래스

* boost::asio::ip::tcp::begin_accept 를 활용하여 클라이언트의 연결을 받고, tcp_connection 객체를 생성한다.

* connection 의 start 함수를 호출한다.

* 완료 된 후, 다음 연결을 받기 위해 다시 accept 를 시작한다. 


3. tcp_connection 클래스

* enable_shared_from_this 를 활용하여 이 객체에 대한 연산이 수행되는 경우 소멸되지 않도록 보장한다.

* 클라이언트로 전달하는 데이터가 소멸되지 않게 하기 위해, 클래스의 멤버로 데이터를 보전한다.

* 전송 데이터가 분리되지 않게 하기 위해 async_write() 함수를 활용

* 사용하지 않는 place_holder 를 제거하여 핸들러 인자의 시그니쳐를 단순히 가져 갈 수도 있다 (async_write)


Full source code

--

#pragma once

#include 
#include 
#include 
#include "boost/shared_ptr.hpp"
#include "boost/asio.hpp"
#include "boost/bind.hpp"
#include "boost/enable_shared_from_this.hpp"

//* enable_shared_from_this 를 활용하여 이 객체에 대한 연산이 수행되는 경우 소멸되지 않도록 보장한다.
//* 클라이언트로 전달하는 데이터가 소멸되지 않게 하기 위해, 클래스의 멤버로 데이터를 보전한다.
//* 전송 데이터가 분리되지 않게 하기 위해 async_write() 함수를 활용
//* 사용하지 않는 place_holder 를 제거하여 핸들러 인자의 시그니쳐를 단순히 가져 갈 수도 있다(async_write)
class TCPConnection : public boost::enable_shared_from_this
{
public:
  typedef boost::shared_ptr pointer;
  static pointer create(boost::asio::io_service& io)
  {
    return pointer(new TCPConnection(io));
  }

  boost::asio::ip::tcp::socket& socket()
  {
    return socket_;
  }

  void start()
  {
    message_ = make_daytime_string();

    boost::asio::async_write(socket_, boost::asio::buffer(message_),
      boost::bind(&TCPConnection::handle_write, shared_from_this()));
  }

private:
  TCPConnection(boost::asio::io_service& io) : socket_(io)
  {
  }

  void handle_write()
  {
    std::cout << message_ << ", write_done" << std::endl;
  }

  std::string make_daytime_string()
  {
    std::time_t now = time(0);
    return ctime(&now);
  }

  boost::asio::ip::tcp::socket socket_;
  std::string message_;
};

//* boost::asio::ip::tcp::begin_accept 를 활용하여 클라이언트의 연결을 받고, tcp_connection 객체를 생성한다.
//* connection 의 start() 함수를 호출한다.
//* 완료 된 후, 다음 연결을 받기 위해 다시 accept 를 시작한다.
class TCPServer
{
public:
  TCPServer(boost::asio::io_service& io) :
    acceptor_(io, boost::asio::ip::tcp::endpoint(
      boost::asio::ip::tcp::v4(), 13))
  {
    start_accept();
  }
private:
  void start_accept()
  {
    TCPConnection::pointer new_connection =
      TCPConnection::create(acceptor_.get_io_service());
    // async_accept(socket, ACCEPT_HANDLER);
    acceptor_.async_accept(new_connection->socket(),
      boost::bind(&TCPServer::handle_accept, this, new_connection, boost::asio::placeholders::error));
  }
  void handle_accept(TCPConnection::pointer new_connection,
    const boost::system::error_code& error)
  {
    if (!error)
    {
      new_connection->start();
    }
    start_accept();
  }

  boost::asio::ip::tcp::acceptor acceptor_;
};

//* tcp_server 에 io_service 를 전달하여 생성하고, tcp_server 클래스를 initiate 한다.
//* 메인 스레드에서 tcp_server 에서 사용하는 io_service 의 run() 함수 호출
void server_run()
{
  try 
  {
    boost::asio::io_service io_service;
    TCPServer server(io_service);
    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }
}



저작자 표시 비영리 변경 금지
신고