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;
  }
}



Learn You Some Erlang

Programming/Erlang 2016. 3. 9. 00:53

몰랐는데, 

learn you ~ 

시리즈북이 원래 있었나보다. (하스켈도 있다고 그러고. )


근래에 몇달째 functional programming 과 Rx 쪽으로 스터디가 집중되다보니, 

말이 펑셔널 펑셔널 하게 짠다지, 좀 잘해보려면 공부를 많이 해야할 것 같아서 .. (현업에서 쓰고있는 부분은 없으니... )

learn You some erlang

http://learnyousomeerlang.com/


을 팀내에서 세미나 형태로 공유하고 있다. - 라고 쓰고 그냥 팀분들께 하는 사기극일지도..-


내용을 잘 정리해서 블로깅을 하거나,  아예 제대로 ? 한글로 번역 - 해서 출판하고 싶은 욕망이 샘솟긴 하는데.. 

(출판할 수 있을까 ? - 이 끝없는 명예욕....) 


일단은 급한대로 한주 한주 세미나를 진행하는 것으로 만족. 


다음 github repo 에 매주 발표자료를 업로드 하고 있다. 

https://github.com/nolleh/learnyouerlang


다음은 1주차 내용.

https://github.com/nolleh/learnyouerlang/blob/master/week1/week1.pdf


뭔가....첨엔 뭐이렇게 문법이 직관적이지가 않아.. 라고 했다가.. 

6주째 자료를 만드는 지금은

얼랭 짱짱맨인듯...


IO Type

Programming/Haskell 2015. 5. 20. 10:18


IO

Introduction to IO

IO a 

사이드 이펙트를 수행하고, 그 결과로 type a 의 value 를 리턴하는 abstract 타입을 고려할 수 있다.

statements 는 보통 사이드 이펙트를 통해 대부분 communicate 된다. I/O 모나드를 소개함으로써 하스켈에서 statement/action 의 개념을 소개한다. 그러나 이 개념 또한 expression 의 일환이라, 결합이 가능하다. (composition)

IO Char 

어떤 사이드이펙트를 수행하고 결과로 some character 를 리턴

IO ()

이 imperative type 에서도 가장 imperative 한 것이 IO () side effect 만 수행함

getChar :: IO Char
getChar :: () -> Char -- () -> something means trying to simulate laziness
putChar :: Char -> IO ()
return :: a -> IO a  -- 어떤것도 하지 않고 즉시 리턴 

a :: IO (Char, Char)
a = do x <- getChar
        getChar
        y <- getChar
        return (x,y)

standard input 으로 부터 3 문자를 읽어 첫번째, 세번째를 페어로 리턴

오른쪽에는 t 의 IO 가 위치하고 왼쪽에는 t 가 위치하여 두 타입이 다르므로, assign 연산자 대신 <- 를 사용하는 것.

getLine :: IO String
getLine = do x <- getChar
            if x == '\n' then
                return []
            else
                do xs <- getLine
                   return (x:xs)

이번엔 standard output 에 string 을 print 하는 IO 메서드

putStr :: String -> IO ()
putStr [] = return ()
putStr (x:xs) = do putChar x
                   pubStr xs

putStrLn :: String -> IO ()
putStrLn xs = do putStr xs
                 putChar '\n'

imperative code 나 I/O 코드를 다른 함수들과 사용할 수 있다는 데 그 강점이 있다. 특히 String 은 그 자체로 리스트이기때문에, 리스트에 사용되는 함수는 위와같은 방식으로의 활용도가 높다.

strlen :: IO ()
strlen = do putStr "Enter a string: "
            xs <- getLine
            putStr "The string has "
            putStr (show (length xs))
            putStrLn " characters"

pure code 를 작성하여 impure code 에 embeded 한 예라 할 수 있음.

'Programming > Haskell' 카테고리의 다른 글

Parser in Haskell  (0) 2015.05.18
Parser 의 bind  (0) 2015.05.15