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 


실행결과 




Tutorial 5. Synchronising handlers in multithreaded programs

Programming/Boost asio 2015. 4. 11. 23:00

다음 내용에 기반함 

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


이번 튜토리얼 에서는 boost::asio::strand 클래스를 사용하여 멀티스레드 프로그램에서 콜백 핸들러를 동기화하는 방법을 알아보자.

이전 4개의 튜토리얼에서는 io_service::run() 함수를 오로지 하나의 스레드에서만 호출하였기 때문에 핸들러 동기화 이슈를 피할수 있었다. ( asio 라이브러리는 콜백 핸들러들이 io_service::run() 을 호출한 스레드에서만 실행될 수 있도록 보장해준다. 거기에, io_service::run() 을 하나의 스레드에서만 부름으로써, 동시에 실행되지 않는 것을 보장받은 것이다.) 


스레드를 하나만 이용하여 접근하는 것은 asio 를 이용한 어플리케이션을 시작하기에 좋았지만, 서버와 같은 프로그램에서 다음과 같은 제한이 존재한다는 것이다. 


* 핸들러가 많은 시간을 소요할 경우 응답성이 떨어진다.

* 멀티 프로세서 시스템에서 확장성이 없다.


이 문제를 해결하기 위해, io_service::run() 을 스레드 풀에서 호출하는 것으로 차선책을 찾아볼 수 있다.

그러나, 이렇게 되어 핸들러가 동시에 실행되는 것이 가능하게 되어, 스레드 safe 하지 않은 공용되는 데이터에 접근하는 핸들러들을 동기화하는 방법이 필요하게 된다.


이전 튜토리얼과 마찬가지로 Printer 를 정의하는 것에서부터 시작해보자. 타이머를 동시에 두개 돌리는 것으로 확장해볼 것이다.

class printer
{
public:


여기에 boost::asio::deadline_timer 멤버 쌍을 초기화 하는 코드와 boost::asio::strand 타입인 stand_ 멤버를 초기화하는 코드를 추가한다. strand 를 통해 디스패치 되는 핸들러들이 다음에 호출 되는 핸들러가 시작되기 전에 끝나는 것을 보장한다. 

이건 몇개의 스레드가 io_service::run() 을 호출 했냐와는 관계가 없다. 물론, 핸들러들은 동시 다발적으로 다른 핸들러들에서 실행 되어 boost::asio::strand 로 인해 디스패치 되지 않을 것이며, 다른 boost::asio::strand 객체를 통해 디스패치 되지 않을 것이다. 


  printer(boost::asio::io_service& io)
    : strand_(io),
      timer1_(io, boost::posix_time::seconds(1)),
      timer2_(io, boost::posix_time::seconds(1)),
      count_(0)
  {


비동기 동작들을 초기화 할때, 각각의 콜백들은 boost::asio::strand 객체를 통해 "wrapped" 된다. strand::wrap() 함수는 핸들러를 포함한 객체를 boost::asio::strand 객체를 통해 새롭게 반환한다. 핸들러를 동일한 boost::asio::strand 를 통해 'wrapping' 함으로써, 동시에 실행되지 않음을 보장할 수 있다.


    timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1, this)));
    timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2, this)));
  }

  ~printer()
  {
    std::cout << "Final count is " << count_ << "\n";
  }


멀티스레드 프로그램에서, 비동기 동작들은 공유 리소스에 접근할때는 동기화가 이루어 져야한다. 이 튜토리얼에서는, 핸들러 (print1 과 print2 ) 에서 공유되는 리소스가 std::cout 과 count_ 라고 하자.

  void print1()
  {
    if (count_ < 10)
    {
      std::cout << "Timer 1: " << count_ << "\n";
      ++count_;

      timer1_.expires_at(timer1_.expires_at() + boost::posix_time::seconds(1));
      timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1, this)));
    }
  }

  void print2()
  {
    if (count_ < 10)
    {
      std::cout << "Timer 2: " << count_ << "\n";
      ++count_;

      timer2_.expires_at(timer2_.expires_at() + boost::posix_time::seconds(1));
      timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2, this)));
    }
  }

private:
  boost::asio::io_service::strand strand_;
  boost::asio::deadline_timer timer1_;
  boost::asio::deadline_timer timer2_;
  int count_;
};


메인 함수에서는 이제 io_service::run() 이 두개의 스레드에서 호출되게 된다 : 하나는 메인스레드, 하나는 추가된 스레드에서 - 이것은 boost::thread 객체를 통해 이루어진다. 


싱글스레드에서 그랬던 것처럼 io_service::run() 에 대한 동시적 호출은 work 가 가 남아 있는 동안 실행되게 된다. 백그라운드의 스레드는 비동기 작업이 완료되기 전까지 완료되지 않는다.


int main()
{
  boost::asio::io_service io;
  printer p(io);
  boost::thread t(boost::bind(&boost::asio::io_service::run, &io));
  io.run();
  t.join();

  return 0;
}


full-source code



사용 코드 - main


실행 결과




Tutorial 4. member function handler

Programming/Boost asio 2015. 3. 17. 23:42

다음에 기반함


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


이전 튜토리얼과 달라진 점은 클래스로 교체하면서 timer 와 count 를 멤버변수로 변경하고, 이에 따라 불필요해진 timer 와 count 함수인자를 제거했다. 

(이 튜토리얼 과정을 정확히 인지하지 않은채로 포스팅하다보니 이번 포스팅에서 배우는 내용인 멤버함수의 bind 호출과 같은 내용이 이미 소개가 되어 큰 의미가 없는 튜토리얼이 됐다. 

그냥 흘깃 넘어가자.)


MemberHandler


 
main 함수


결과