• 2006-03-22

    Half Sync Half Async Pattern

    IOCP의 경우 입출력 요청은 비동기적으로 이루어지지만 커널 내부에서 존재하는 큐를 통해 GQCS()를 호출하는 쓰레드들에게는 동기적으로 할당하게 된다. 이처럼 비동기 레이어와 동기 레이어, 그리고 이를 연결시켜주는 메시지큐로 구성되는 패턴을 Half Sync Half Async Pattern 이라고 부른다. 이 패턴은 다음과 같은 곳에 적용되었다.

    • select() (UNIX/Linux) - 커널에서 입출력 처리는 비동기적으로 이루어지지만, 이들은 유저 프로세스에서 순차적으로 받아서 처리한다.
    • IOCP & ThreadPooling (Windows)
    • ACE_Task (ACE)

    see also :

  • 2006-03-22

    Active Object Pattern

    Active Object Pattern, 일명 능동 객체 패턴은 ACE_Task (ACE), ActiveObject (JAVA), LuaTask (LUA) 등에 적용된 패턴으로, 쓰레드간의 비동기적인 함수 호출을 '''객체지향적으로''' 구현한 패턴이다.

    로깅을 담당한 쓰레드에게 비동기적으로 로그를 남기는 함수를 구현한다고 가정해보자.

    
    
    /// async log
    Logger::Log("Hello World!");
    
    void Logger::Log(LPCTSTR msg)
    {
    m_Queue.Add( new AsyncLog(msg,this) );
    }
    
    DWORD Logger::Run()
    {
    while(true)
    {
    AsyncWork * pWork = m_Queue.Get();
    pWork->Execute();
    SAFE_DELETE(pWork);
    }
    }
    
    class AsyncLog : public AsyncWork {
    public :
    AsyncLog( LPCTSTR msg, Logger * pLogger )
    : m_Message(msg), m_pLogger(pLogger)
    {
    }
    void Execute()
    {
    m_pLogger->SafeLog(m_Message);
    }
    };
    
    

    요청하는 쪽은 로그 쓰레드에서 로그 메써드를 호출토록 하기 위해서 요청을 객체에 넣어서 큐에 삽입하게 되고, 로그 쓰레드는 큐에서 이 객체를 끄집어내어서 관련 메써드를 호출하게 된다. ACE에서는 비동기 함수 호출의 결과를 나타내는 Future 를 지원한다. 이걸 잘 쓰면, 해당 함수 호출이 잘 끝났는지, 결과는 무엇인지 polling 등을 통해서 알아낼 수 있다.

    see also :

  • 2006-03-21

    ACE Tips

    ACE_Message_Block

    • duplicate()는 recursive 로 동작한다. 따라서 메시지 블럭 리스트를 순회하면서 선택적으로 duplicate() 할 수는 없다.
    • release() 역시 recursive 로 동작한다.
    • release()는 new 로 생성한 객체에 대해서만 호출해야 한다. 권고에 따르면 가능한한 메시지 블럭은 힙에 생성해서 사용하는게 좋다고 한다.
    • 실제 쌓여 있는 데이터의 크기를 알려면 size() 대신 length() 를 써야 한다.
    • 생성자에 버퍼와 크기를 넘겨주더라도 이것이 내부적으로 복사되지는 않으며 단지 포인터만을 가지게 된다. 이때 wr_ptr() 을 수동으로 늘려줘야 한다.
    • copy()는 append()로 동작하지만 버퍼를 자동적으로 늘리지는 않는다.
    • copy(char*)의 경우 널문자도 함께 버퍼에 쓰게 된다. 따라서 copy(str.c_str(),str.size())로 하는 것이 안전하다.
    • reset() 은 rd_ptr 과 wr_ptr 을 초기화한다. 단 base 로 가기보다는 0 으로 초기화된다는데 주의.
    • data_block() 을 이용해서 내부 데이터 블럭을 교체할 경우 rd_ptr 과 wr_ptr 을 수동으로 설정해줘야 한다.
    • ACE_Message_Block(char*,int) 생성자는 겉보기에는 copy()가 발생하는 것 같지만 실제로는 단지 데이터 블럭이 포인터만을 갖게 되므로 복사가 발생하지 않는다. 가령 ACE_Message_Block("abcde",5) 의 경우 내부 데이터 블럭은 문자열 상수의 주소값을 가지게 된다. 또한 wr_ptr() 역시 수동으로 설정해줘야 하는 것으로 미루어 보건데, 아무래도 read only 용으로만 사용해야 할 것 같다.

    ACE_Asynch_Acceptor/Connector

    • ACE_Asynch_Acceptor 와 ACE_Asynch_Connector 의 make_handler 는 기본적으로 new 를 해서 리턴한다. 만약 활성화된 연결들을 관리하려면 이 함수를 오버라이드해야 한다.
    • 비동기 accept 를 여러 개 준비하려면 단지 ACE_Asynch_Acceptor::open()의 파라미터만을 많이 주면 된다. 굳이 핸들러 여러 개를 먼저 만들어놓을 필요는 없다. (내부적으로 여러 개의 ACE_HANDLE 을 많이 만든 다음 연결이 이루어진 직후에 핸들러를 만들어서 넘겨준다)

    ACE_Task

    • ACE_Task_Base + ACE_Message_Queue = ACE_Task
    • ACE_Activation_Queue 와 ACE_Method_Request 를 이용하면 Active Object Pattern(함수의 호출과 실행을 분리시키는 패턴)을 적용할 수 있다.
    • ACE_Activation_Queue::dequeue() 등 timeout 을 지정할 수 있는 wait 함수의 경우 대체로 상대 시간이 아닌 절대 시간을 사용한다. 따라서 ACE_OS::gettimeofday() 등으로 현재 시간을 어디에선가 얻어온 다음 시간을 계산해줘야 하는 귀차니즘이 있다.

    Reactor

    • ACE_Reactor_Notification_Strategy 는 Reactor::notify(handler,mask)를 호출해주는 strategy 패턴 클래스이다. ACE_Message_Queue 의 경우, 모든 enqueue 멤버 함수의 끝에 notify() 를 호출하는데 이를 통해서 reactor 로 하여금 특정 핸들러의 특정 콜백을 호출토록 할 수 있다.
    • ACE_WFMO_Reactor 의 경우 62개 핸들 제한이 존재하므로, 수백-수천개의 연결을 관리하려면 ACE_Select_Reactor 와 ACE_TP_Reactor 를 사용해야 한다. 단 윈도우에서 성능은 그다지 좋지 않으므로 proactor 로 만드는 편이 좋을 것이다.

    ACE_Acceptor/Connector

    • ACE_Event_Handler 와 함께 사용될 수 없다. 오직 ACE_Svc_Handler 와만 가능

    CDR

    • 모든 primitive type 의 경우 구조체처럼 바이트 정렬이 된다. { char, int } 와 { char, short, int } 모두 8 byte 를 차지한다. ACE_LACKS_CDR_ALIGNMENT 를 이용하면 정렬을 하지 않을 수 있다.
    • string 의 경우 [길이]+[바디]로 직렬화되는데 이때 길이를 위해서 unsigned long 를 사용한다. 내부적으로 스트링의 길이를 측정하기 위해 strlen 이 호출된다. (뜨아)
    • from_string() 과 to_string()의 경우 최대 길이를 넘겨 줘야 한다.
    • 배열이나 문자열을 위한 버퍼는 내부적으로 new 를 통해서 할당된다.
    • write_string() 의 경우 (바이트 크기+1)만큼 전송되는 반면, write_wstring() 은 그냥 전송된다. 즉 "hello" 는 (4+5+1)=10 바이트로 전송되지만 L"hello" 는 (4+5*2)=14 바이트로 전송된다.
    • 이미 존재하는 메시지 블럭을 ACE_InputCDR로 읽되 메시지 복사를 피하고 싶다면 ACE_InputCDR(data_block,flag,rd_pos,wr_pos) 생성자를 사용해야 한다. ACE_InputCDR(msg_block)의 경우 내부 메시지 블럭으로 복사가 일어난다는 점에 유의할 것.
    • 이미 존재하는 메시지 블럭에 ACE_OutputCDR로 쓰려면 ACE_OuptutCDR(mb) 를 사용하면 된다. 내부적으로 duplicate()가 호출된다.

    ACE_Get_Opt/ACE_ARGV

    • ACE_Get_Opt 는 UNIX 스타일의 command line parameter 의 파싱을 도와준다. 이때 옵션 문자열의 포맷이 중요한데, "h:kl:" 은 -h param1 -k -l param2 형식을 의미한다.
    • ACE_ARGV 는 어떤 문자열을 argc, argv 로 바꿔주며, 그 결과를 ACE_Get_Opt 에게 넘겨서 커맨드라인 파라미터를 파싱하는 것처럼 해결할 수 있다. 특히 환경파일에 저장된 옵션 스트링을 파싱할 때 사용하면 좋다.

    misc

    • define ACE_NTRACE 0 를 켜면 해당 파일에서 참조하는 ACE 프레임워크에 대해 트레이스가 남는다. 만약 전역으로 켜고 싶다면 stdafx.h 에다 넣을 것.

    • ACE_TMAIN 을 쓸 수 없는 경우 ACE::init() 와 ACE::fini() 를 수동으로 불러줘야 한다.
  • 2006-02-24

    MMOSG - Massive Multiplayer Online Sports Game

    가마수트라에서 검색을 하다가 2005년 어떤 회사가 online extreme sports 게임을 만들고 있다는 기사를 읽었다. 자세히는 몰라도 눈덮인 높은 산에서 수 백 여명이 스키나 보드를 타고 내려오면서 온갖 묘기를 한다고 생각하니 꽤 재미있을 듯하다. (왜 내려오는지에 대해서는 아무 생각이 없다)

    우리야 팀플레이가 중시되는 스포츠이기에 MMOSG로 만들기엔 좀 무리가 있다만, 심리스 월드를 돌아다니다가 마음 맞는 사람끼리 경기장으로 가서 야구도 하다가 축구도 하고 미식축구도 할 수 있고, 할 일 없으면 그냥 관중이 되어서 구경하다가 빈 물병도 던져보고, 진짜 열받는 날에는 옷을 모두 벗고 축구 경기장으로 뛰어들다가 감옥에 가서 일주일동안 로그인 금지도 당할 수 있는 그런 MMOSG라면 꽤 매력적(이라고 쓰고 투자할 사람이 많을것 같다라고 읽는다 -_-)이지 않을까?

    그런 의미에서, 선배 MMOFPS들의 서비스 노하우만 어디서 잘 훔쳐올 수 있다면 정말 좋겠다. 흣흣

  • 2006-02-18

    네트워크 액션 게임의 분류

    네트워크 액션 게임들을 peer 간 연결, 동기화 처리, 서버 유무 등으로 구분지어보면 대략 다음과 같다.

    • asynchronous P2P : 모든 peer 들이 1:1로 연결되며, 적당히 정보를 주고 받으며 최신의 정보들만을 적용하는 방식이다. 디아블로1 등 액션 게임에서 주로 사용되며, 속도는 빠르지만 보안성이 떨어진다. 접속자 숫자가 비교적 많은 편. 패킷 손실에도 비교적 안전하다.

    • synchronous P2P : 모든 peer 들이 1:1로 연결되는 것은 맞지만 액션 정보를 전송한 후 일정 시간 후에 모든 peer 에서 동시에 실행하는 방식. RTS등 동기화가 치명적인 게임에서 사용되며, 최대 접속자가 10여명 안팎으로 제한된다. 무손실 UDP 를 주로 사용한다.

    • client/server : 각 peer 들은 서버와 직접 연결되어, 모든 판정은 dedicated 서버상에서 이루어지는 방식. 서버는 주기적으로 스냅샷 정보를 모든 클라이언트에게 브로드캐스트한다. 퀘이크나 하프라이프, 언리얼 같은 FPS 게임에서 주로 사용하는 방식. 비동기 처리와 패킷 손실이라는 점은 asynchronous P2P와 유사하다. P2P가 아니므로 홀펀칭이라든지 NAT 때문에 골치 썩는 일이 없어서 좋다.

    • hosted client/server : 디아블로2의 배틀넷이나 MMOFPS와 같이 client/server 모델에서 사용자가 서버를 운영하는 대신 회사에서 직접 관리하는 방식이다. 서버 운영 및 트래픽 때문에 돈이 무지 많이 들기 때문에 웬만한 회사에서는 엄두도 못낸다. 그리고 서버 다운이 되면 온갖 원성을 다 사게 되므로 보통 약관을 애매모하게 쓰는 편이다. 유일한 장점은 보안성이 가장 높다는 점.

    • N-P2P : 앞으로 우리가 구현할, 1-4의 장점만을 취하고 단점은 버린, 즉 보안성도 좋고 빠른 액션도 가능하면서도 동기화도 잘맞는 가운데 30명 이상(1)의 연결도 지원, 그리고 보이스 채팅(2)과 대규모 spectator 모드도 지원하면서 트래픽도 낮은 방식.

    • 20명 이상의 플레이어를 지원하려면 P2P로는 힘들지 않을까? 연결 노드가 도대체 몇 개야..

    • 그나저나 보이스 채팅은 한때는 vaporware 에 가까웠는데, 요즘은 꽤 많은 게임에서 지원하고 있다.