• 2008-02-18

    Getting Real

    Getting Real은 단지 웹개발에 대한 지침이기도 하지만, 쌓여 있는 버그와 뒤죽박죽한 요구 사항 속에서 고민하는 게임 개발자에게도 꽤 유익한 내용이다. 가끔씩 길을 잘못 들고 있지 않을까 하는 불안감이 들 때마다 읽어봐야겠다.
    ```
    "혁신은 '아니오' 에서 시작된다."

    [혁신]은 1000가지 일에 대해서 '아니오'라고 말하므로써 잘못된 길을 가거나 불필요하게 너무 많은 일을 하지 않도록 하는 것에서 시작됩니다. 우리는 항상 새롭게 진입가능한 시장에 대해서 고민합니다. 하지만 그것은 과감히 '아니오'라고 말하므로써 실질적으로 중요한 일들에 대해서만 집중할 수 있을 때 가능합니다.
    —Steve Jobs, CEO, Apple (from The Seed of Apple's Innovation)

    "천 개의 기능은 필요하지 않습니다."

    스티브 잡스가 아이튠즈에 대해서 독립음반회사 사람들을 모아놓고 조그만 발표를 했습니다. 그 날 많은 사람들은 쉴 새없이 손을 들면서 어떤 기능이 제공되는 지, 혹은 어떤 기능을 추가할 계획이 있는 지 물었습니다. 마침내 쟙스가 말했습니다. "잠깐만요. 손을 잠시 내리고 제 말을 들어보시죠. 저는 여러분이 아이튠즈에 있으면 좋을 만한 아이디어를 수 천개 가지고 있다는 것을 잘압니다. 하지만 우리는 수 천개의 기능을 원하지 않습니다. 그렇게 하면 엉망이 될 겁니다. 혁신은 모든 것에 대해서 '예'라고 말하는 것이 아닙니다. 혁신은 정말 중요한 것을 제외한 나머지에 대해서는 '아니오'라고 말하는 것입니다."

    —Derek Sivers, 대표 및 프로그래머, CD Baby and HostBaby(from Say NO by default)```
    그러고 보면  헛똑똑이질을 하다가 매번 revert를 하다가 저런 글을 보고 뉘우치곤 하는 나보다, 이런 류의 글따위는 한번도 읽어보진 않아도 뭐가 중요한지 몸으로 깨닫고 있는 모군이 2g 정도 대견스럽긴 하다. ㅋㅋ

  • 2008-02-10

    boost::serialization

    boost::serialization 은, 템플릿 멤버 함수 하나 또는 free function 하나만 정의하면, 해당 클래스를 다양한 스트림으로 직렬화할 수 있게 해준다. 특히 list - vector - map - set 등의 다양한 STL 컨테이너들을 지원한다는 것이 장점이다.

    이때, 매크로 없이 & 연산자 만으로 읽고 쓰기를 가능하게 했는데, 이건 템플릿 아카이브 파라미터가 알아서 잘 읽거나 쓰도록 책임을 전가했다는 게 상당히 아름답다.. 단, virtual 이 아니라서 하위 클래스는 모두 이걸 정의해줘야 한다. 그리고 아직 hash_map 은 잘 지원하지 않는 모양인데, 뭔가 복잡한 사정이 있는 듯하다.

    주의할 점이라면 아래 예제처럼 쓰려는 객체가 const 여야 한다는 점 정도?

    
    #include "stdafx.h"
    
    #include <fstream>
    
    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    
    #include <boost/archive/binary_oarchive.hpp>
    #include <boost/archive/binary_iarchive.hpp>
    
    #include <boost/serialization/list.hpp>
    #include <boost/serialization/vector.hpp>
    #include <boost/serialization/map.hpp>
    #include <boost/serialization/hash_map.hpp>
    
    class chat_message
    {
        friend class boost::serialization::access;
    public:
    
        chat_message() {}
    
        chat_message(unsigned short id, unsigned short body_length, string msg, wstring wmsg)
            : id_(id)
            , body_length_(body_length)
            , message(msg)
            , wmessage(wmsg)
        {
            for ( size_t i = 0 ; i < 10 ; i ++ )
            {
                string val = boost::str(boost::format("Hello %1%")%i);
                messages[i] = val;
                array_[i] = val;
                list_.push_back(i);
                vector_.push_back(i);
                map_[i] = val;
                hash_map_[val] = (rand()%2==0 ? true : false);
            }
        }
    
        template<class Archive>
        void serialize(Archive & ar, const unsigned int version)
        {
            ar & id_;
            ar & body_length_;
            ar & message;
            ar & wmessage;
            ar & messages;          // pritimive array
            ar & array_.elems;      // boost::array
            ar & list_;             // std::list
            ar & vector_;           // std::vector
            ar & map_;              // std::map
            //ar & hash_map_;
        }
    
        void check_equal( const chat_message & r ) const
        {
            BOOST_CHECK_EQUAL(id_,r.id_);
            BOOST_CHECK_EQUAL(body_length_,r.body_length_);
            BOOST_CHECK_EQUAL(message,r.message);
            BOOST_CHECK(wmessage==r.wmessage);
    
            for ( size_t i = 0 ; i < 10 ; i ++ )
            {
                BOOST_CHECK_EQUAL(messages[i],r.messages[i]);
            }
    
            for ( size_t i = 0 ; i < array_.size() ; i ++ )
            {
                BOOST_CHECK_EQUAL(array_[i],r.array_[i]);
            }
    
            {
                BOOST_REQUIRE_EQUAL( list_.size(), r.list_.size() );
                list<float>::const_iterator itr = list_.begin();
                list<float>::const_iterator itr2 = r.list_.begin();
                for ( ; itr != list_.end() ; itr ++, itr2++ )
                {
                    BOOST_CHECK_EQUAL( *itr, *itr2 );
                }
            }
    
            {
                BOOST_REQUIRE_EQUAL( vector_.size(), r.vector_.size() );
                vector<int>::const_iterator itr = vector_.begin();
                vector<int>::const_iterator itr2 = r.vector_.begin();
                for ( ; itr != vector_.end() ; itr ++, itr2++ )
                {
                    BOOST_CHECK_EQUAL( *itr, *itr2 );
                }
            }
    
            {
                BOOST_REQUIRE_EQUAL( map_.size(), r.map_.size() );
                map<int,string>::const_iterator itr = map_.begin();
                map<int,string>::const_iterator itr2 = r.map_.begin();
                for ( ; itr != map_.end() ; itr ++, itr2++ )
                {
                    BOOST_CHECK_EQUAL( itr->first, itr2->first );
                    BOOST_CHECK_EQUAL( itr->second, itr2->second );
                }
            }
    
            //{
            //  BOOST_REQUIRE_EQUAL( hash_map_.size(), r.hash_map_.size() );
            //  stdext::hash_map<string,bool>::const_iterator itr = hash_map_.begin();
            //  stdext::hash_map<string,bool>::const_iterator itr2 = r.hash_map_.begin();
            //  for ( ; itr != hash_map_.end() ; itr ++, itr2++ )
            //  {
            //      BOOST_CHECK_EQUAL( itr->first, itr2->first );
            //      BOOST_CHECK_EQUAL( itr->second, itr2->second );
            //  }
            //}
        }
    private :
        unsigned short id_;
        unsigned short body_length_;
        string message;
        wstring wmessage;
        string messages[10];
        boost::array<string,10> array_;
        list<float> list_;
        vector<int> vector_;
        map<int,string> map_;
        stdext::hash_map<string,bool> hash_map_;
    };
    
    // TODO
    // - archive from/to buffer
    // - user defined archive
    // - string, wstring load/save
    // - stl support (list,vector,map)
    
    BOOST_AUTO_TEST_CASE(test_serialize)
    {
        const chat_message msg1(35, 59, "Hello World!",L"Welcome!");
    
        {
            chat_message msg2;
    
            std::ofstream ofs("serialize.txt");
            boost::archive::text_oarchive oa(ofs);
            oa << msg1;
            ofs.close();
    
            std::ifstream ifs("serialize.txt");
            boost::archive::text_iarchive ia(ifs);
            ia >> msg2;
            ifs.close();
    
            msg1.check_equal(msg2);
        }
    
        {
            chat_message msg2;
    
            std::ofstream ofs("serialize.bin", std::ios::binary);
            boost::archive::binary_oarchive oa(ofs);
            oa << msg1;
            ofs.close();
    
            std::ifstream ifs("serialize.bin", std::ios::binary);
            boost::archive::binary_iarchive ia(ifs);
            ia >> msg2;
            ifs.close();
    
            msg1.check_equal(msg2);
        }
    
        {
            chat_message msg2;
    
            boost::asio::streambuf buf;
            ostream os(&buf);
            boost::archive::text_oarchive oa(os);
            oa << msg1;
    
            istream is(&buf);
            boost::archive::text_iarchive ia(is);
            ia >> msg2;
    
            msg1.check_equal(msg2);
        }
    
        {
            chat_message msg2;
    
            boost::asio::streambuf osf;
            boost::archive::binary_oarchive oa(osf);
            oa << msg1;
    
            boost::archive::binary_iarchive ia(osf);
            ia >> msg2;
    
            msg1.check_equal(msg2);
        }
    
        // via istream/ostream
        {
            chat_message msg2;
    
            boost::asio::streambuf buf;
            ostream os(&buf);
            boost::archive::binary_oarchive oa(os);
            oa << msg1;
    
            istream is(&buf);
            boost::archive::binary_iarchive ia(is);
            ia >> msg2;
    
            msg1.check_equal(msg2);
        }
    
    }
    
  • 2008-02-09

    boost::asio iostream

    문자열 기반의 네트워크 입출력 프로그래밍을 해야 한다면, ASIO 의 iostream 을 사용하면 간단히 해결된다.

    boost::asio::io_service 같이 모호한 객체 선언도 필요없고, 연결 관리나 버퍼링 같은 개념도 적당히 무시할 수 있어서, 예외 처리만 잘 한다면 원격 로그라든지 XML RPC  클라이언트 같은 곳에 써먹을 수 있을 것 같다.

    만약 복잡한 문자열 조작이 필요하다면 boost::string_algo 정도면 충분하고, 정규식이 필요하면 boost::regexp 을 가져다 사용하면 될 듯하다.

    
    
    // 문자열 기반의 네트워크 입출력 스트림
    BOOST_AUTO_TEST_CASE( test_iostream )
    {
        string http_request =
            "GET / HTTP/1.0\r\n"
            "Host: reiot.cafe24.com\r\n\r\n";
        string expected_http_response[] = { "HTTP/1.0 200 OK", "HTTP/1.1 200 OK" };
    
        {
    
            tcp::iostream io_("reiot.cafe24.com","http");
    
            io_    << http_request << flush;    // or io_.flush();
    
            vector<string> http_response;
            while ( !io_.eof() )
            {
                string line;
                getline(io_,line);
                http_response.push_back(line);
            }
    
            BOOST_CHECK_EQUAL( boost::trim_copy(http_response[0]), expected_http_response[1] );
    
            io_.close();
        }
    
        {
            string host = "www.google.co.kr";
            tcp::iostream io_(host,"http");
    
            io_    << "GET / HTTP/" << 1 << "." << 0 << endl
                << "Host: " << host << endl << endl
                << flush;    // or io_.flush();
    
            vector<string> http_response;
            while ( !io_.eof() )
            {
                string line;
                getline(io_,line);
                http_response.push_back(line);
            }
    
            BOOST_CHECK_EQUAL( boost::trim_copy(http_response[0]), expected_http_response[0] );
        }
    
        {
            tcp::iostream io_("www.naver.com","http");
    
            io_    << "GET / HTTP/1.0" << endl
                << "Host: www.naver.com" << endl
                << endl
                << flush;    // or io_.flush();
    
            stringstream response;
            while ( !io_.eof() )
            {
                char ch = io_.get();
                response << ch;
            }
    
            string line;
            getline(response,line);
            BOOST_CHECK_EQUAL( boost::trim_copy(line), expected_http_response[1] );
        }
    
        {
            tcp::iostream io_("clien.career.co.kr","http");
    
            io_    << "GET / HTTP/1.0" << endl
                << "Host: clien.career.co.kr" << endl
                << endl
                << flush;    // or io_.flush();
    
            stringstream response;
            while ( !io_.eof() )
            {
                char buf[128];
                io_.read(buf,127);
                size_t nread = io_.gcount();
                if ( nread > 0 )
                {
                    buf[nread] = 0;
                    response << buf;               
                }
            }
    
            string line;
            getline(response,line);
            BOOST_CHECK_EQUAL( boost::trim_copy(line), expected_http_response[1] );
        }
    }
    
    
  • 2008-02-09

    boost::test thread safe

    다른 표준 라이브러리와 마찬가지로, boost::test 역시 멀티쓰레드에 대해서 안전하지 않다. 물론 BOOST_CHECK 등의 테스트 매크로들은 내부적으로 싱글톤을 사용하기 때문에, 쓰레드 함수 내부에서 사용하더라도 실패할 경우 에러 메시지를 출력해주지만, 다른 쓰레드에서 동일한 순간에 역시 같은 매크로를 호출하게 되면 콜스택 깊은 곳에서 난감한 에러를 뱉어버린다.

    이걸 해결하려면 결국 mutex 로 테스트 매크로들을 wrap 한 또다른 매크로를 만들어서 써야 하는 수 밖에 없다.

    
    #define SAFE_BOOST_CHECK_EQUAL(m,x,y) { \
    boost::mutex::scoped_lock lock(m); \
    BOOST_CHECK_EQUAL(x,y); }
    
    void boost_check_always( boost::mutex * m )
    {
    boost::timer timer_;
    
    while(timer_.elapsed() < 10.f )
    {
    SAFE_BOOST_CHECK_EQUAL(*m,1,1);
    }
    
    }
    
    BOOST_AUTO_TEST_CASE( test_boost_check_always )
    {
    boost::mutex m;
    boost::thread t(boost::bind(boost_check_always,&m));
    
    boost::timer timer_;
    while(timer_.elapsed() < 10.f )
    {
    SAFE_BOOST_CHECK_EQUAL(m,1,1);
    }
    
    t.join();
    }
    
  • 2008-02-04

    boost::asio TCP resolver

    한가로운 주말, 최신 BOOST 를 vc81로 빌드하고 ASIO를 만지작거려 봤다. 대충 훑어본 바, 이 정도면 지금 코드에서 충분히 ACE를 제거해도 될 수준의 기능인 듯하다. 어차피 둘 다 디버깅 들어가면 템플릿 사이에서 헤매이는 건 마찬가지니...

    
    // TODO multiple ip address in localmachine (..localmachine)
    BOOST_AUTO_TEST_CASE( test_resolver )
    {
     boost::asio::io_service io_service;
     boost::system::error_code error;
     tcp::resolver resolver(io_service);
     tcp::resolver::iterator end_;
    
     // localmachine
     {
      tcp::resolver::query query(boost::asio::ip::host_name(),"");
      tcp::resolver::iterator iterator = resolver.resolve(query);
      tcp::endpoint _endpoint = *iterator;
      BOOST_CHECK_EQUAL( _endpoint.address().to_string(), "192.168.10.101" );
     }
    
     // host -> ip
     {
      tcp::resolver::query query("reiot.cafe24.com","");
      tcp::resolver::iterator iterator = resolver.resolve(query);
      tcp::endpoint _endpoint = *iterator;
      BOOST_CHECK_EQUAL( _endpoint.address().to_string(), "123.214.172.49" );
     }
    
     // localhost
     {
      tcp::resolver::query query("localhost","");
      tcp::resolver::iterator iterator = resolver.resolve(query);
      tcp::endpoint _endpoint = *iterator;
      BOOST_CHECK_EQUAL( _endpoint.address().to_string(), "127.0.0.1" );
      if ( ++iterator != end_ )
      {
       _endpoint = *iterator;
       BOOST_CHECK_EQUAL( _endpoint.address().to_string(), "192.168.10.101" );
      }
     }
    
     // http
     {
      tcp::resolver::query query("reiot.cafe24.com","http");
      tcp::resolver::iterator iterator = resolver.resolve(query);
      tcp::endpoint _endpoint = *iterator;
      BOOST_CHECK_EQUAL( _endpoint.address().to_string(), "123.214.172.49" );
      BOOST_CHECK_EQUAL( _endpoint.port(), 80 );
     }
    
     // ftp
     {
      tcp::resolver::query query("reiot.cafe24.com","ftp");
      tcp::resolver::iterator iterator = resolver.resolve(query);
      tcp::endpoint _endpoint = *iterator;
      BOOST_CHECK_EQUAL( _endpoint.address().to_string(), "123.214.172.49" );
      BOOST_CHECK_EQUAL( _endpoint.port(), 21 );
     }
    
     // telnet
     {
      tcp::resolver::query query("reiot.cafe24.com","telnet");
      tcp::resolver::iterator iterator = resolver.resolve(query);
      tcp::endpoint _endpoint = *iterator;
      BOOST_CHECK_EQUAL( _endpoint.address().to_string(), "123.214.172.49" );
      BOOST_CHECK_EQUAL( _endpoint.port(), 23 );
     }
    
     // unknown host
     {
      tcp::resolver::query query("reiot1.com","");
      resolver.resolve(query,error);
      BOOST_CHECK_EQUAL( error.value(), boost::asio::error::host_not_found );
     }
    
     // unknown service
     {
      tcp::resolver::query query("reiot.cafe24.com","test");
      resolver.resolve(query,error);
      BOOST_CHECK_EQUAL( error.value(), boost::asio::error::service_not_found );
     }
    }
    
    void print_address(const boost::system::error_code& e, tcp::resolver::iterator itr )
    {
     tcp::endpoint endpoint_ = *itr;
     BOOST_MESSAGE( endpoint_.address() );
    }
    
    class AsyncResolveHandler
    {
    public :
     AsyncResolveHandler( int errorcode ) : address_(""), port_(0), errorcode_(errorcode) {}
     AsyncResolveHandler( std::string addr, int port = 0 ) : address_(addr), port_(port) {}
     void operator() (const boost::system::error_code& e, tcp::resolver::iterator itr )
     {
      if ( !e )
      {
       tcp::endpoint endpoint_ = *itr;
       BOOST_CHECK_EQUAL( endpoint_.address().to_string(), address_ );
       if ( port_ != 0 )
       {
        BOOST_CHECK_EQUAL( endpoint_.port(), port_ );
       }
      }
      else
      {
       BOOST_CHECK_EQUAL( e.value(), errorcode_ );
      }
     }
     std::string address_;
     int port_;
     int errorcode_;
    };
    
    BOOST_AUTO_TEST_CASE( test_async_resolver )
    {
     boost::asio::io_service io_service;
    
     tcp::resolver resolver(io_service);
    
     boost::system::error_code error;
    
     // host -> ip
     {
      tcp::resolver::query query("reiot.cafe24.com","");
      resolver.async_resolve(query,AsyncResolveHandler("123.214.172.49"));
      BOOST_CHECK_EQUAL(io_service.run_one(error),1);
      BOOST_CHECK_MESSAGE(!error,error.message());
      io_service.reset();
     }
    
     // localhost -> 127.0.0.1
     {
      tcp::resolver::query query("localhost","");
      resolver.async_resolve(query,AsyncResolveHandler("127.0.0.1"));
      BOOST_CHECK_EQUAL(io_service.run_one(error),1);
      BOOST_CHECK_MESSAGE(!error,error.message());
      io_service.reset();
     }
    
     // http
     {
      tcp::resolver::query query("reiot.cafe24.com","http");
      resolver.async_resolve(query,AsyncResolveHandler("123.214.172.49",80));
      BOOST_CHECK_EQUAL(io_service.run_one(error),1);
      BOOST_CHECK_MESSAGE(!error,error.message());
      io_service.reset();
    
     }
    
     // ftp
     {
      tcp::resolver::query query("reiot.cafe24.com","ftp");
      resolver.async_resolve(query,AsyncResolveHandler("123.214.172.49",21));
      BOOST_CHECK_EQUAL(io_service.run_one(error),1);
      BOOST_CHECK_MESSAGE(!error,error.message());
      io_service.reset();
     }
    
     // telnet
     {
      tcp::resolver::query query("reiot.cafe24.com","telnet");
      resolver.async_resolve(query,AsyncResolveHandler("123.214.172.49",23));
      BOOST_CHECK_EQUAL(io_service.run_one(error),1);
      BOOST_CHECK_MESSAGE(!error,error.message());
      io_service.reset();
     }
    
     // unknown host
     {
      tcp::resolver::query query("reiot1.com","");
      resolver.async_resolve(query,AsyncResolveHandler(boost::asio::error::host_not_found));
      BOOST_CHECK_EQUAL(io_service.run_one(error),1);
      BOOST_CHECK_MESSAGE(!error,error.message());
      io_service.reset();
     }
    
     // unknown service
     {
      tcp::resolver::query query("reiot.cafe24.com","test");
      resolver.async_resolve(query,AsyncResolveHandler(boost::asio::error::service_not_found));
      BOOST_CHECK_EQUAL(io_service.run_one(error),1);
      BOOST_CHECK_MESSAGE(!error,error.message());
      io_service.reset();
     }
    }