Daytime.3 - An asynchronous TCP daytime server

Programming/Boost asio 2016. 4. 3. 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;
  }
}



Daytime.2 - A synchronous TCP daytime server

Programming/Boost asio 2015. 4. 19. 13:05

다음에 기반함

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


이번엔 TCP asio 를 사용하여 어떻게 서버 응용프로그램을 구현할 수 있는지 살펴본다.

#include <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;


클라이언트로 다시 보내줄 문자열을 생성하는 make_daytime_string() 함수를 정의해보겠다.

이 함수는 daytime 서버 응용프로그램 내에서 재사용 될 것이다.

std::string make_daytime_string()
{
  using namespace std; // For time_t, time and ctime;
  time_t now = time(0);
  return ctime(&now);
}

int main()
{
  try
  {
    boost::asio::io_service io_service;


ip::tcp::acceptor 객체는 새로운 커넥션을 listen 하기 위해 생성하였다. IPv4, TCP 포트 13 을 listen 하도록 초기화 했다. 

    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 13));


한번에 하나의 커넥션을 다루는 iterative 서버가 될 것이다. 클라이언트의 커넥션을 표현할 socket 를 생성하고, 커넥션을 기다리도록 하자. 

 for (;;)
    {
      tcp::socket socket(io_service);
      acceptor.accept(socket);


클라이언트가 우리의 서비스에 접속했다. 현재 시간을 확인하고 클라이언트로 정보를 보낸다.

      std::string message = make_daytime_string();

      boost::system::error_code ignored_error;
      boost::asio::write(socket, boost::asio::buffer(message), ignored_error);
    }
  }


마지막으로, 예외를 처리한다. 

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

  return 0;
}


- full_source code



- main



- 실행결과


클라이언트바이너리를 별도로 만들어놓은 후, ( 이전 포스트 ) 서버실행후 클라이언트를 실행했다.




Daytime.1 - A synchronous TCP daytime client

Programming/Boost asio 2015. 4. 13. 01:17

다음 내용에 기반함


Introduction to Sockets

The tutorial programs in this section show how to use asio to develop simple client and server programs. These tutorial programs are based around the daytimeprotocol, which supports both TCP and UDP.

The first three tutorial programs implement the daytime protocol using TCP.


이번 튜토리얼에서는 TCP 클라이언트를 만드는데 어떻게 이용 할 수 있을지 소개한다.

먼저 필요한 헤더파일들을 include 하는 것으로 시작한다.
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>

이 응용프로그램은 daytime 서비스에 접근하는것이며, 따라서 유저가 서버를 특정할 필요가 있다.

using boost::asio::ip::tcp; int main(int argc, char* argv[]) { try { if (argc != 2) { std::cerr << "Usage: client <host>" << std::endl; return 1; }


asio 를 이용하는 모든 프로그램들은 적어도 하나이상의 io_service 객체를 필요로한다. 
    boost::asio::io_service io_service;


파라미터에 지정된 서버 이름을 TCP endpoint 로 변환할 필요가 있는데, 이는 ip::tcp::resolver 객체를 통한다.

  tcp::resolver resolver(io_service);


resolver 는 쿼리 객체를 받아 이를 endpoint 의 리스트로 변환한다. 쿼리이름을 서버이름을 이용하여 생성할 것이며, 이 서비스의 이름은 "daytime" 이다.

리스트는  ip::tcp::resolver::iterator 타입으로 리턴된다. (ip::tcp::resolver::iterator 의 기본 생성자를 통해 생성된 객체는 end iterator 로 사용 될 수 있다)

   tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);


이제 소켓을 생성하고 연결할 차례다. 

얻어진 리스트는 IPv4 와 IPv6 를 모두 포함할 수도 있으므로 동작하는 것을 찾기 위해 각각의 주소들에 대해 시도해야한다. 

이런 사용이, 클라이언트 프로그램이 특정 IP 주소체계와 독립적으로 프로그램을 구성할 수 있게 해준다. 

boost::asio::connect() 함수는 자동적으로 이를 수행해준다.

    tcp::socket socket(io_service);
    boost::asio::connect(socket, endpoint_iterator);


이제 연결이 수립되었다. daytime 서비스의 응답만을 기다리면 된다.


boost::array 를 전달받은 데이터를 저장하는데 사용하도록 하자. boost::asio::buffer() 함수는 자동적으로 배열의 사이즈를 결정해주어 버퍼 오버런이 발생하지 않게 도와준다. boost::array 대신에, char[] 나 std::vector 도 사용할 수 있다.

 for (;;)
    {
      boost::array<char, 128> buf;
      boost::system::error_code error;

      size_t len = socket.read_some(boost::asio::buffer(buf), error);


서버가 연결을 종료했을때, ip::tcp::socket::read_some 함수는 boost::asio::error::eof 에러와 함께 종료될 것이며, 이때 루프를 빠져나가면 된다.

if (error == boost::asio::error::eof) break; // Connection closed cleanly by peer. else if (error) throw boost::system::system_error(error); // Some other error. std::cout.write(buf.data(), len); }


마지막으로, thrown 된 예외를 처리하면 된다. 


full source code




main 


실행결과