ACE Proactor Framework 4 – Echo 서버 만들기


비동기 IO 제어에 필요한 기술 요소
- 작업 요청 객체
- 작업 완료 객체 – ACE_Async_Result 파생 클래스
- 디스패처 객체 – ACE_Proactor
- 이벤트 객체 – ACE_Handler

비동기 IO 제어에 필요한 부수적인 기술 요소
- 버퍼 – ACE_Message_Block
- 핸들 – ACE_HANDLE

** 버퍼 관리 주의 사항
비동기 IO에서는 버퍼 제어권이 두가지입니다. 작업 요청이 이루어지면 버퍼의 제어권은 OS로 넘어갑니다. 작업 완료 단계에서는 다시 프로그래머에게 제어권이 넘어옵니다. 따라서 마지막에 버퍼를 소멸하는 과정에서는 필히 해당 버퍼를 사용할 가능성이 있는 작업 요청 객체의 cancel 함수를 호출해서 버퍼 제어권를 프로그래머로 전환한 후 소멸해야 합니다.

실제 Proactor 기반 서버에서는 제한된 버퍼만으로도 충분히 효과적입니다. 따라서 필요 이상으로 많은 버퍼를 사용할 필요가 없으며, 가능하면 버퍼를 재사용하는 습관을 가져야 합니다. 특히 버퍼 관리자를 도입해서 제한된 메모리에서 효율적으로 버퍼를 관리하면 성능 향상에 좋습니다. 확실한 사실은 Proactor 기반 서버에서는 버퍼 관리를 세밀하게 제어하면 여러 잇점이 존재합니다.

PAcceptor.h
#pragma  once
#include  <ace/Asynch_IO.h>
#include  <ace/INET_Addr.h>
#include  <ace/SOCK_Acceptor.h>
#include  <ace/Message_Block.h>
#include  <ace/Proactor.h>

class PAcceptor:public ACE_Handler
{
public:
    PAcceptor(ACE_Proactor* actor, const  char* addr);
    ~PAcceptor(void);


   virtual  void handle_accept (const ACE_Asynch_Accept::Result &result);

   int run();

private:
    ACE_Asynch_Accept require_;
    ACE_INET_Addr addr_;
    ACE_SOCK_Acceptor acceptor_;
    ACE_Message_Block msg_;
};

PAcceptor.cpp
#include  "PAcceptor.h"
#include  <iostream>

#include  "EchoService.h"

PAcceptor::PAcceptor(ACE_Proactor* actor, const  char* addr)
    :ACE_Handler(actor),addr_(addr),msg_( ( sizeof (sockaddr_in) + 16 ) * 2 )
{
    acceptor_.open( addr_ );
   this->require_.open(*this,acceptor_.get_handle(),0, this->proactor());
}


PAcceptor::~PAcceptor(void)
{
    require_.cancel();
}

void 
PAcceptor::handle_accept (const  ACE_Asynch_Accept::Result &result){
    std::cout<<std::endl<<"Enter Client:"<<result.accept_handle();
   EchoService* svc = new  EchoService( this->proactor(), result.accept_handle());
    run();
}

int 
PAcceptor::run(){
   return  this->require_.accept(msg_,0);
}

EchoService.h
#pragma  once
#include  <ace/Asynch_IO.h>
#include  <ace/SOCK_Stream.h>
#include  <ace/Message_Block.h>
#include  <ace/Proactor.h>

class  EchoService:public  ACE_Handler
{
public:
    EchoService(ACE_Proactor* actor, const  ACE_HANDLE& handle);
    ~EchoService(void);

   virtual  void handle_read_stream (const  ACE_Asynch_Read_Stream::Result &result);
   virtual  void handle_write_stream (const  ACE_Asynch_Write_Stream::Result &result);

   int run();

private:
   ACE_Asynch_Read_Stream read_require_;
   ACE_Asynch_Write_Stream write_require_;

   ACE_SOCK_Stream stream_;
   ACE_Message_Block msg_;
};

EchoService.cpp
#include  "EchoService.h"
#include  <iostream>

EchoService::EchoService(ACE_Proactor* actor, const  ACE_HANDLE& handle)
    :ACE_Handler(actor), stream_(handle), msg_(1024)
{
   this->read_require_.open(*this, stream_.get_handle(),0,this->proactor());
   this->write_require_.open(*this, stream_.get_handle(),0,this->proactor());
    run();
}


EchoService::~EchoService(void)
{
   this->read_require_.cancel();
   this->write_require_.cancel();
}


int 
EchoService::run(){
   return  this->read_require_.read( msg_, msg_.capacity());
}

void 
EchoService::handle_read_stream (const  ACE_Asynch_Read_Stream::Result &result){
   if( !result.success()){
       std::cout<<std::endl<<"Error:"<<result.error();
      delete  this;
      return;
   }
   if( 0==result.message_block().length() ){
       std::cout<<std::endl<<"Client Exist";
      delete  this;
      return ;
   }

   this->write_require_.write( result.message_block(), result.message_block().length());
}

void 
EchoService::handle_write_stream (const  ACE_Asynch_Write_Stream::Result &result){
   if( !result.success()){
       std::cout<<std::endl<<"Error:"<<result.error();
      delete  this;
      return;
   }

   if( 0==result.message_block().length() ){
      result.message_block().reset();
      this->read_require_.read(result.message_block(), result.message_block().capacity());
      return;
   }

   this->write_require_.write(result.message_block(),result.message_block().length());
}

PEchoServer.cpp
// PEchoServer.cpp : Defines the entry point for the console application.
//

#include  "stdafx.h"

#include  <ace/ACE.h>
#include  "PAcceptor.h"

int  _tmain(int  argc, _TCHAR* argv[])
{
    ACE::init();
   {
      PAcceptor ac(ACE_Proactor::instance(), "192.168.0.2:1000");
       ac.run();

      ACE_Proactor::instance()->proactor_run_event_loop();
   }
    ACE::fini();


   return 0;
}

- 목록:

21 thoughts on “ACE Proactor Framework 10 – Acceptor-Connector 예제 2

  1. 시리얼 통신강의를 빼고 모든 강의를 전부 시청하였습니다.
    이렇게 좋은 강의 자료를 만들어 주셔서 감사합니다.
    상용화 해도 손색이 없겠네요^^

  2. 훌륭한 강의에 감사드립니다.
    예제를 따라하다가 ACE::init()과 ACE::fini() 를 헤더에서 찾을 수 없다는 에러를 내었습니다.
    저는 ACE-6.3.1을 사용하였는데, ACE-6.1.9 버전으로 테스트 해 보니 또 잘 되더라구요.
    6.3.1 에서는 include 아래에 를 추가해서 문제를 해결했습니다.
    무슨 연유에서인지 저는 ACE.h 가 Init_ACE.h 를 포함하지 않았나봅니다.
    혹시 같은 고민을 하시는 저 같은 초보자가 있을까 하여 로그를 남깁니다.

    • 제가 아는 선에서는 예전 5 버전대에서는 ace/ace.h 에 초기화 로직이 있었던걸로 기억합니다.
      그런데 6점대로 넘어가면서 ace/ace.h -> ace/ACE.h 등으로 세분화? 되면서
      ACE::init(), ACE::fini()가 ace/Init_ACE.h로 나뉜것 같더라고요.

  3. 안녕하세요. 질문을 드리고자합니다.^^
    제가 ace를 이용한 네트워크 프로그램하나를 인수받았는데요.
    클라이언트를 62개 이상 받아들이면 오류가 나는겁니다. 그래서 찾아보니
    ACE_Reator 를 사용하여 개발된 중계서버 문제. 수백 또는 수천개의 핸들을 다중 수신하도록 설정이 가능한 ACE_Select_Reator, ACE_TP_Reator와는 달리, ACE_WFMO_Reator는 62개 이상의 핸들은 처리할 수 없다. 이 제한은 Windows에서는 단지 WaitForMultipleObject()함수의 스레드당 대기 가능 핸들 개수가 64개라는 사실때문이다.

    ACE_WFMO_Reator는 내부적으로 64개의 핸들중 2개를 별도로 사용하기 때문에 62개의 핸들만 사용가능하고, Windows에서 ACE_Reator를 사용시에는 디폴트로 ACE_WFMO_Reator가 Base 이다.

    -이 증상을 해결하려면 ACE_Proactor를 사용하거나, ACE_Select_Reator를 사용해야한다.

    이렇게 나와있더라구요. 저는 ace를 이용해본적이 없어서 급한마음에 책을 보고 ACE_Select_Reator로 대처를 하려고 하는데요.

    m_pAcceptor = new ClientAcceptor;
    m_pAcceptor->reactor( ACE_Reactor::instance() );

    이부분을
    ACE_Select_Reactor sr;
    m_reactor = new ACE_Reactor(&sr);// ACE_Reactor* m_reactor;
    m_pAcceptor->reactor( ACE_Reactor::instance(m_reactor) );
    이렇게 수정하니 62개이상의 클라이언트 오류는 사라졌지만..

    m_reactor->reset_reactor_event_loop();
    m_reactor->run_reactor_event_loop();

    이부분에서 죽더라구요 ㅜㅜ

    ace 강의를 심도있게 들은 후 공부좀 해서 하고싶지만 시간이 없는 관계상 이렇게 질문드려봅니다.
    어떤 다른 부분을 수정해야 하는지 궁금합니다.
    이렇게 질문부터 드려서 죄송합니다.

    • 일단 기존 64개 이상 핸들링 문제는 잘 해결하신 듯하군요.

      전체 코딩을 보지 않아서 정확하게 답변드리긴 힘들어보이는군요.

      일단 reset_reactor_event_loop를 호출하는 이유는 파악하셔야 할듯 합니다. 일반적으로 run_reactor_event_loop만 호출만 되는데 .. 말이죠.

      일단 reset_reactor_event_loop를 호출하면 sr이 비활성화 상태로 들어갑니다. 비활성화 상태가 되면, 자신이 관리하던 핸들러에 대해 소거 작업이 이후로 진행될 터인데, 이 과정에서 오류가 발생하는 듯하군요.

      결국 reset_reactor_event_loop를 호출해야하는 이유를 파악하셔야 할듯합니다. 어지간해서는 호출할 일이 없을 듯한데.

      • 아.. 답변 감사합니다~ 꾸벅~
        알고보니 제가 기초적인 실수를 했습니다. ㅎㅎ
        m_reactor->reset_reactor_event_loop();
        m_reactor->run_reactor_event_loop();
        이부분이 svc() 스레드에서 돌아가는것인데..

        ACE_Select_Reactor sr;
        m_reactor = new ACE_Reactor(&sr);// ACE_Reactor* m_reactor;
        이부분은 스레드 돌아가기전 제가 만든 init()함수에서 작성한 코드라..
        레퍼런스로 넘긴 sr의 핸들이 소멸되버려서 발생한 런타임 purecall()오류인것이였습니다.

        그래서..
        m_tp = new ACE_TP_Reactor;
        m_reactor = new ACE_Reactor(m_tp);
        m_pAcceptor->reactor(m_reactor);

        멤버변수로 생성하여..

        m_reactor->reset_reactor_event_loop();
        m_reactor->run_reactor_event_loop();
        이렇게 하니 아주 잘됩니다. ㅎㅎ
        헬프 답변 너무 감사드립니다.

        올려주신 강의를 하나 보았는데 정말 감명받았습니다.ㅜㅜ
        나머지 강의들도 보면서 모두 만들어 보려합니다.
        정말 존경스럽습니다.
        다시한번 감사드립니다. (–)(__)

  4. 이런 질문을 드려도 될지 모르겠는데…

    Service Configurator Framework 2 – ACE_Service_Config 클래스 이해 에서
    remove Commander 로 자기자신을 죽일 때

    if( 0 == in[len-1]){
    std::cout<<std::endl<<"User Command:"<<msg_.rd_ptr()<< std::endl;
    ACE_Service_Config::process_directive(msg_.rd_ptr());
    std::cout<<"after process_directive"<<msg_.rd_ptr()<<std::endl;
    msg_.length(0);
    msg_.crunch();
    }

    process_directive 를 거치면서, 프로그램이 죽네요.. 디버깅을 타보니, msg_.rd_ptr() 이 directive[] 로 연결이 되는데, 이 포인터가 의미가 없는 값이 들어있네요.. Bad_ptr 로…
    제 생각으로는, Commander 가 죽으면서, Bad_ptr 이 되어버린거 같은데…
    어떻게 해결할 수 있을지, 감이 잘 안옵니다… ㅠㅠ;

    • fini() 까지는 호출이 되고 나서 죽습니다…
      ServiceLoader 에서 스트링을 참조하려다가 죽는듯한데…
      (글 수정이 안되는게 좀 불편하네요~ㅎㅎ, 강의, 감사하게 보고 있습니다.)

      • svc.conf 파일이 정상적인지 일단 확인하셔야 할 듯합니다.
        단순 문자열 관련 에러라면, 조금만 노력하시면 에러를 잡아낼 수 있을 겁니다.
        조금 더 자세한 정보를 제공해준다면, 조금 더 도움이 되는 답변이 가능합니다.

      • 저도 같은 문제거 같습니다. fini()까지 호출되고 main()에서 error가 나서 죽습니다.
        혹시 알고 계시면 알려주세요~~ 감사합니다. 강의 감사 드립니다.

  5. 안녕하십니까.
    이런질문도 드려도 될지…

    제가 window mfc dialog 기반에서 Proactor을 이용해 서버를 만들려고 하는데

    ACE_Asynch_Acceptor의 open 함수를 호출하면 정상적으로 동작은 되는데, 종료시(런타임시..)
    memory leak이 발생합니다.

    mfc 가 아닌 win32 console 로 프로젝트를 생성하면 memory leak이 발생하지 않습니다.
    혹시 이 문제에대한 해결방법이 있을까요? 도움이 필요합니다?

    감사합니다.

    • 해당 momory leak 현상은 proactor thread 종료된 후
      accept close가 호출됨으로 cancel io 대한 처리 부분이 에러가 발생한 경우일듯 합니다.
      (close도 요청되는 요청 작업이고, 해당하는 응답 처리가 되어야 하지만, 이미 쓰레드가 죽었음으로 이런 경우엔 불가능합니다.)

      따라서 accept close 후 proactor thread 종료로 변경시 해당 leak은 발생하지 않을 듯 합니다.

  6. 윈도우OS 에서 사용할 ACE 공유 메모리 class 가 있을까요? ACE_Shared_Memory_SV 는 open 에러가 나네요..

Leave a Reply to piljoo Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>