- 원문링크 :
http://www.cs.wustl.edu/~schmidt/ACE_wrappers/docs/tutorials/001/page01.html
- 개인적으로 쓸데없는 말은 과감하게 잘라버렸습니다. 직역이라 번역에는 조금 자신이 없습니다. (특히 acceptor부분은...=_=a)
1 시작하며 #
이 튜토리얼의 목적은 다중 클라이언트 연결을 다룰수 있는 정말 간단한 서버를 생성하는 법을 보여준다. "전통적인" 서버 어플리케이션과는 달리, 이 예제는 혼자서 한 프로세스안에서 모든 요청을 처리한다. 멀티프로세싱과 멀티쓰레딩은 이후의 튜토리얼에서 다룰 것이다.
2 소스의 개괄적 구조 #
우리가 서버를 생성하기 위해서는 무엇이 필요할까?
- 클라이언트들로부터 접속을 받아들이는(accept) 어떤 것
- 설치된 접속들을 제어하는 어떤 것
- 이것들 모두를 제어하는 메인 프로그램 루프
ACE의 EventHandler는 두번째 요구사항을 풀어준다. 이것은 지금은 당장 명확하게 보이진 않을 것이다. 그러나 우리가 이 튜토리얼을 계속 진행하면 점점더 확실하게 알 수 있을 것이다.
마지막으로, 간단한 main() 함수안에서는 프로그램 루프가 제공될 것이다. 어떤 프로그램 초기화 작업 이후에는 EventHandler상의 데이타 "이벤트" 또는 Acceptor로 접근된 접속들을 대기하기 위한 무한 루프로 진입하게 된다.
3 Reactor? #
계속하기 전에, 한가지 이상의 ACE 개념에 대해 안내하고자 한다 : Reactor
이 시점에서 Reactor가 무엇인지, 어떤 일을 하는지, 어떻게 작동하는지에 대해서는 상세하게 설명하지는 않으려한다. 처음 우리가 접하는 코드내의 Reactor의 기본적인 함수들을 이해하는 데 필요한 것들만 다루려한다. 이 도해는 Reactor와 Accepter, 어플리케이션 handler간의 상호관계를 표현한 것이다.
간결하게 정의한다면, reactor는 다른 객체에 "어떤 사건"이 발생할 때 반응하는 객체이다. "어떤 사건"을 우리는 이벤트라고 부른다. 다른 객체들은 reactor에 등록해놓은 통신객체들이라고 할 수 있다. 등록시점에서 어떤 종류의 이벤트에 관심있는지도 또한 정의하게 된다. reactor는 선택한 이벤트들이 등록된 객체내에 발생할때 운영체계에 의해 그 여부를 알려준다. reactor는 이때 이벤트를 처리할 등록된 객체에 대한 맴버함수들을 사용한다. reactor가 이벤트가 무엇때문에 발생했는지에 대해서는 알고있지 않는다는 것에 주의해라. 이것은 이벤트가 정확하게 실행될 수 있도록 하는 인식장치에 불과하다. reactor는 단순히 이벤트의 객체만을 알려줄 뿐이다.
왜 reactor를 사용할까? 튜토리얼이 진행되면 더욱 확실하게 알 수 있게 되겠지만, 일단 지금은 어쨌거나 이것에 대한 명백한 답은 다음이 될 듯 하다 : reactor는 동시에 다발적인 클라이언트 연결들이 싱글 쓰레드 방식의 서버에 의해서 효과적으로 처리되도록 한다.
서버들은 전통적으로 각 클라이언트를 전담하는 별도의 쓰레드나 프로세스를 생성해놓는다. 큰 규모의 서비스(telnet이나 ftp)에 대해서는 이 방법이 어울린다. 어쨌거나, 작은 규모의 서비스들에서는 실제 처리될 작업보다도 프로세스의 과부하가 더 부담되는 현상이 생긴다. 따라서...클라이언트들을 다루기위해서 프로세스대신에 쓰레드를 사용하는 것으로 이야기 되곤 한다. 이것도 또한 괜찮은 방법이다. 허나 아직 몇가지 경우에 있어서는 발생하는 과부하가 너무 많다. 대신에, 왜 소수의 클라이언트들을 다루는 단일 쓰레드를 가지지 않는가? 그리고 클라이언트당 한개의 쓰레드/프로세스를 할당하는 방식보다 더욱 똑똑한 로드밸런싱 방법을 사용하지 않는 것일까? 여기서 경고하자면 : 한 프로세스의 하나의 쓰레드내에서 모든 요청들을 처리하는 것은 실제로 단지 거의 순간적으로(instantaneously) 요청들이 처리될 수 있을때에만 효율적이다.
이것은 reactor의 힘과 융통성이 어디서 작용하는지에 대한 해답이다. 개발자는 단순한 싱글쓰레드 어플리케이션을 생성할 수 있고 이것은 나중에 클라이언트-쓰레드 방식, 클라이언트-프로세스, 또는 쓰레드 풀 솔루션으로 수정될 수 있다.
4 메인 루프 #
여기에서 우리는 메인 프로그램 루프로 넘어가도록 하자. 이것은 시작하기에 아주 알맞은 간단한 코드이다. 메인 프로그램은 정말로 매우 간단하다. 실제 작업이 ACE에서 파생된 클래스들로 완료된다.
Kirthika Parameswaran이 이 튜토리얼의 요약을 제공해주었다 :
이것은 간단한 기록(logging) 서버 예제이다. reactor는 클라이언트당 한개의 쓰레드를 설정하는 것 대신에 단일한 쓰레드상에서 한개이상의 클라이언트 요청을 처리하기 위해 사용된다. reactor는 이벤트에 반응하며 "콜백함수"을 사용하여 그 이벤트에 대해 알맞게 등록된 Event_Handler로 이벤트를 디멀티플렉싱 처리한다. reactor는 발생예정인 모든 이벤트들을 다루는 무한 이벤트 루프안에서 실행된다.
Logging_Acceptor는 지정된 서버 포트 주소로부터 listen처리를 수행하고 수동적으로 접속요청을 기다린다. Acceptor는 또한 reactor에 등록되어있다. This way it is simply yet another Event_Handler for the Reactor and hence no special processing is needed for it.
한번 접속요청이 발생하면, Acceptor는 그것을 받아들이고(accept) 접속을 수용한다. reactor 인스턴스는 핸들러로 이것을 전달한다. 이것을 수행하기 위해서 accept에 대한 핸들러가 등록되어있는데, 이것은 ACE_Event_Handler::ACCEPT_MASK을 사용해서 실행할 수 있다.
Logging_Client는 또다른 Event_Handler이다. 이것은 handle_input()메소드를 사용하여 직접 클라이언트들의 요청을 처리한다. 이것은 또한 ACE_Event_Handler::READ_MASK를 사용해서 reactor에 등록되어있다.
Event_Handlers는 handle_close()매서드를 사용하거나 명시적으로 remove_handler()매서드를 호출하여 reactor로부터 등록해제될 수 있다.
FYI (from Doug):
The ACCEPT_MASK is defined in the ACE_Event_Handler class. It's used to inform the Reactor that you want to register an event handler to "accept" a connection passively. Not surprisingly, the ACE_Acceptor component uses this.
The READ_MASK is also defined in the ACE_Event_Handler class. It's used to inform the Reactor that you want to register an event handler to "read" data from an established connection.
// $Id: ACE_c6_a9_c5_e4_b8_ae_be_f3001_2fACE_c5_f8_c5_b6_c0_bb_bb_e7_bf_eb_c7_cf_b1_e2_c0_a7_c7_d1_c3_ca_ba_b8_c0_da_b0_a1_c0_cc_b5_e5,v 1.7 2005/09/28 06:48:30 redpixel Exp redpixel $
/* reactor가 정의되어있는 헤더화일을 Include한다. */
#include "ace/Reactor.h"
/* 간단하게, 우리는 reactor를 전역변수로 생성한다.
튜토리얼 후반에 우리는 더 확실하게 이 작업을 할 것이다.
어쨌거나 이 튜토리얼의 목적은 접속을 accept시키고 처리하는 것을
안내하고자 하는 것이지, reactor의 전체 기능을 설명하고자하는
것이 아니다. */
ACE_Reactor *g_reactor;
/* 우리의 accept 객체를 정의한 헤더화일을 Include한다.
acceptor는 클라이언트로부터 접속을 서버가 "받아들이는"
것을 위한 객체를 말한다. */
#include "acceptor.h"
/* TCP/IP 서버는 접속 요청을 위한 단지 하나의 포트만을 listen 할 수 있다.
다음은 포트 번호를 나타낸다.*/
static const u_short PORT = ACE_DEFAULT_SERVER_PORT;
int
main (int, char *[])
{
/* reactor 인스턴스를 생성한다. 다시말하면, 전역 포인터는 실제로
이것을 다루는 가장 좋은 방법은 아니지만 여기서는 간단한 예를 들기
위해 사용했다. 나중에 이것을 논하도록 하자. ACE_NEW_RETURN 매크로
를 사용하는 법에 주목하자. 만약 new 연산이 실패하면 1이 리턴된다. */
ACE_NEW_RETURN (g_reactor,
ACE_Reactor,
1);
/* reactor와 마찬가지로, 주소(ADDR)객체에 대해서는 일단 자세히 설명하지
않기로 한다. 이 객체가 제공하는 것은 네트워크상의 주소 서비스에 대한
추상화이다. 이 시점에서 우리가 알아야한 모든 것은 우리는 주소객체를
생성한다는 것이다. 이것은 신규 접속요청을 listen할 TCP/IP 포트를
지정한다. */
ACE_INET_Addr addr (PORT);
Logging_Acceptor *peer_acceptor;
/* 우리는 이제 acceptor 객체를 생성했다. 이 시점에서는
객체가 "작동중"으로 open된 상태가 아니므로 어떤 접속도
아직 구축되지 않는다.*/
ACE_NEW_RETURN (peer_acceptor,
Logging_Acceptor,
1);
/* acceptor가 이제 open되는 부분이다. 여러분은 대부분의 ACE 객체들이
사용되기 전에 open()이 실행되어야만 한다는 것을 발견할 수 있을 것이다.
여기에서의 open()이 호출되려면 우리는 '주소'객체를 통해서 접속을
listen 하는 곳을 알려주어야만 한다. 또한 그것이 "g_reactor"
인스턴스에 등록되도록 인자에 적어준다. */
if (peer_acceptor->open (addr, g_reactor) == -1 )
ACE_ERROR_RETURN ((LM_ERROR,
"Opening Acceptor\n"),
-1);
ACE_DEBUG ((LM_DEBUG,
"(%P|%t) starting up server logging daemon\n"));
/* reactor의 handle_events 맴버 함수는 모든 등록된 객체들을 검색하고
관심있는 이벤트가 발생했을 경우 알맞은 맴버함수를 실행시키는 의무를
가지고 있다. 이벤트가 실행되면 handle_events 함수는 return한다. 모든
이벤트를 얻을 목적으로, 우리는 이것을 무한 루프에 넣도록 한다.
현재 실행화일이 무한루프에 빠져있는 까닭에 프로그램을 종료하려면
CTRL-C를 누를 필요가 있다. */
for (;;)
g_reactor->handle_events ();
return 0;
}
말했듯이, 메인 프로그램은 정말로 아주 간단하다. ^^; 다음이 위 소스에 대한 순서도이다.
- 우리가 listen처리를 하기 원하는 포트를 위한 주소를 생성하기
- 주소에서 listen처리를 수행할 accepter를 생성하기
- 접속요청에 응답할 acceptor를 reactor에 등록하기
- reactor가 이벤트를 처리할 수 있도록 무한루프 진입
5 acceptor? #
이제 우리는 acceptor 객체를 찾아보도록 한다.
Kirthika가 다음과 같은 비유를 들어주었다:
사무실을 한번 상상해보도록 하자 :
- Reactor : 접수업무담당자
- Event_Handlers: 특별한 요청사항들을 만족시키는 다양한 부서들
- SERVER_PORT : 문
- Acceptor : 문지기
// $Id: ACE_c6_a9_c5_e4_b8_ae_be_f3001_2fACE_c5_f8_c5_b6_c0_bb_bb_e7_bf_eb_c7_cf_b1_e2_c0_a7_c7_d1_c3_ca_ba_b8_c0_da_b0_a1_c0_cc_b5_e5,v 1.7 2005/09/28 06:48:30 redpixel Exp redpixel $
#ifndef _CLIENT_ACCEPTOR_H
#define _CLIENT_ACCEPTOR_H
/* SOCK_Acceptor는 소켓 접속을 accept하는 법을 가지고 있다.
우리는 이들중 하나를 Logging_Acceptor의 핵심부에서 사용할
것이다. */
#include "ace/SOCK_Acceptor.h"
#if !defined (ACE_LACKS_PRAGMA_ONCE)
# pragma once
#endif /* ACE_LACKS_PRAGMA_ONCE */
/* Event_Handler는 ACE_Reactor에 등록되어지는 객체이다. 이벤트가
일어나면, reactor는 Event_Handler로 callback한다. */
#include "ace/Event_Handler.h"
/* 클라이언트가 접속할 때, 접속을 관리할 Logging_Handler를 생성한다.
여기 그 선언을 include 한다. */
#include "logger.h"
/* Logging_Acceptor는 ACE_Event_Handler로부터 상속받는다. 이것은
reactor가 acceptor를 모든 다른 핸들러와 같이 취급하도록 만든다.*/
class Logging_Acceptor : public ACE_Event_Handler
{
public:
/* 이런 간단한 경우에서는 우리는 생성자와 소멸자로 지루해지기 싫다.
실제 어플리케이션이라면 특별하게 관리해야할 수도 있다. */
/* 이것은 main()에서 호출했던 open() 메소드이다. 이것을 구현하려면
두가지를 실행해야한다 : (1) acceptor를 open한다. 이렇게 해서
클라이언트의 요청을 받아들일 수 있다. (2) 자기자신을 reactor에
등록하여 클라이언트의 요구에 응답할 수 있도록 만든다. */
int open (const ACE_INET_Addr &addr,
ACE_Reactor *reactor)
{
/* acceptor의 open()을 실행한다. 우리는 main()에서 우리가 listen
하기를 원하는 주소를 넘겨준다. 두번째 인자는 주소를 재사용해도
"좋다"고 알려준다. 이것은 때때로 타임아웃되지 않은 닫힌 연결을
고려하기 위해 필요하다. */
if (this->peer_acceptor_.open (addr, 1) == -1)
return -1;
/* 우리가 사용할 reactor 인스턴스를 기억해둔다. 이것은 나중에
클라이언트 접속용 핸들러를 생성하는데 필요하다. */
reactor_ = reactor;
/* 이제 우리는 주어진 reactor를 등록할 수 있다. reactor의 포인터가
전역으로 선언되어있기 때문에 우리는 이것을 단지 사용만 하면 된다.
ACE_Event_Handler로 부터 상속받았기 때문에 'this'을 직접 넘길 수
있다는 것에 주목하라. ACCEPT_MASK를 사용하는 것은 reactor에게
클라이언트로부터의 접속요구에 대해서 알아차리도록 설정하는 것이다. */
return reactor->register_handler (this,
ACE_Event_Handler::ACCEPT_MASK);
}
private:
/* 멀티 플랫폼 추상화를 제공하기 위해서, ACE는 접속 종단점(connection
endpoint)을 위해 "핸들"의 개념을 사용한다. UNIX에서는 이것은 전통적인
화일 지시자(file descriptor) 또는 정수이다. 다른 운영체계에서는 또다른
무엇이 될 수도 있다. reactor는 자신의 내부적 필요성에 의해서 핸들을
얻을 필요가 있다. 여기서 적절한 핸들은 acceptor 객체의 핸들이다.*/
ACE_HANDLE get_handle (void) const
{
return this->peer_acceptor_.get_handle ();
}
/* accept 요청이 도착하면, reactor는 handle_input() 콜백을 실행시킨다.
이곳이 접속요청을 다루어야 하는 코드 위치이다. */
virtual int handle_input (ACE_HANDLE handle)
{
/* reactor에 의해 우리에게 전달된 핸들은 상위 호출(up-call)을
일으키는 원인이 된다. 약간 앞선 입장에서 바라본다면 실제로 다중
접속을 처리하는데 있어서 하나의 핸들러만을 등록할 경우도 있다.
_handle 인자는 그것들을 해결하는 방법이다. 여기서는 이 인자를
사용하지 않기 때문에 단순히 ACE_UNUSED_ARG() 매크로를 사용해서
이것을 무시하기로 한다. */
ACE_UNUSED_ARG (handle);
Logging_Handler *svc_handler;
/* 접속 요구에 응답하여, 우리는 새로운 Logging_Handler를 생성한다.
이 신규 객체는 클라이언트가 접속이 끊길때 까지 상호 통신하기 위한
목적으로 사용된다. ACE_NEW_RETURN 매크로를 사용하는 방식에 주의하자.
new 연산이 실패하면 -1을 반환한다. */
ACE_NEW_RETURN (svc_handler,
Logging_Handler,
-1);
/* 연결을 완료하기 위해서 acceptor 객체의 accept() 메소드 호출을
실행하고, 접속 핸들러 인스턴스에 그것을 넘긴다. 이것은 접속의
"소유권"을 acceptor로부터 연결 핸들러로 전송한다. */
if (this->peer_acceptor_.accept (*svc_handler) == -1)
ACE_ERROR_RETURN ((LM_ERROR,
"%p",
"accept failed"),
-1);
/* 다시 말하지만, 대부분의 객체들은 사용하기전에 open()해두는 것
이 좋다. 우리는 핸들러에게 이벤트에 대한 등록을 할 수 있도록
reactor 포인터를 넘길 것이다. 만약 open()이 실패하면 close()를
강제로 실행한다. */
if (svc_handler->open (reactor_) == -1)
svc_handler->close ();
return 0;
}
protected:
/* acceptor 객체 인스턴스 */
ACE_SOCK_Acceptor peer_acceptor_;
/* reactor 포인터를 담아둘 변수 */
ACE_Reactor *reactor_;
};
#endif /* _CLIENT_ACCEPTOR_H */
여기서 우리가 이 객체를 개발하는 데 있어 거의 어플리케이션 위주가 아닌 코딩을 하고 있다는 것을 주목하는 것은 중요하다. 사실, 만약 우리가 진행정보를 제거한다면, accept함수로 전달할 신규 Logging_Handler 객체를 생성할 때는 단지 어플리케이션 위주의 코드만 남게 된다.
(It is important to notice here that we have done very little application-specifc code in developing this object. In fact, if we take out the progress information, the only app-specific code is when we create the new Logging_Handler object to give to the accept function.)
이 코드 전체에 걸쳐서 왜 C++ 템플릿이 없는지 의아해할지도 모른다. 실제로 ACE 툴킷에은 다음과 같은 구문이 선언되어있다 :
typedef ACE_Acceptor <YourHandlerClass, ACE_SOCK_ACCEPTOR> YourAcceptorClass;우리는 이것처럼 사용하는 것과 같다 :
typedef ACE_Acceptor <Logging_Handler, ACE_SOCK_ACCEPTOR> Client_Acceptor;
이것은 앞에서 보여준 것과 유사한 코드 조각을 생성할 것이다. 주요 차이점은 템플릿에 의해 생성된 handle_input는 reactor에 handler를 등록되지 "않는"다는 점이다. 긴 안목을 가지고 본다면, 이 방법이 더욱 괜찮은 방법이다. 왜냐하면 우리는 Logging_Handler의 open함수로 로직을 옮길 수 있고, 완벽하게 제네릭한 accepter를 사용할 수 있기 때문이다. 이제 우리는 접속요청을 accept하는 방법을 알았다. 이런 괜찮은 템플릿 선언을 알았는데도 불구하고, 우리는 계속해서 위에서 "손수" 작성된 acceptor를 사용할 것이다. 앞서 언급한대로, 단하나의 차이점은 접속 handler의 open함수 안에 있다.
6 logging handler 객체 #
// $Id: ACE_c6_a9_c5_e4_b8_ae_be_f3001_2fACE_c5_f8_c5_b6_c0_bb_bb_e7_bf_eb_c7_cf_b1_e2_c0_a7_c7_d1_c3_ca_ba_b8_c0_da_b0_a1_c0_cc_b5_e5,v 1.7 2005/09/28 06:48:30 redpixel Exp redpixel $
#ifndef _CLIENT_HANDLER_H
#define _CLIENT_HANDLER_H
/* 접속 핸들러도 또한 ACE_Event_Handler로부터 상속받는다.
그러므로 이것은 reactor에 등록할 수 있다.*/
#include "ace/Event_Handler.h"
#if !defined (ACE_LACKS_PRAGMA_ONCE)
# pragma once
#endif /* ACE_LACKS_PRAGMA_ONCE */
#include "ace/INET_Addr.h"
/* 우리는 TCP/IP를 사용하기 때문에, 연결처리를 위해
SOCK_Stream이 필요하다. */
#include "ace/SOCK_Stream.h"
class Logging_Handler : public ACE_Event_Handler
{
public:
/* acceptor와 같이 생성자와 소멸자는 무시한다. */
/* 클라이언트 핸들러를 open하기 위해서, 자기사진을 reactor에
등록해야만 한다. ACE_SOCK_Stream 맴버 변수를 "open"할
필요가 없다는 것에 주목해라. 왜? acceptor의 accept() 매소드를
호출하는 것이 우리가 원하는 바를 해주기 때문이다.*/
int open (ACE_Reactor *reactor)
{
/* reactor를 기억해두자. */
reactor_ = reactor;
/* 여기서는 READ_MASK를 사용한다. */
if (reactor->register_handler (this,
ACE_Event_Handler::READ_MASK) == -1)
ACE_ERROR_RETURN ((LM_ERROR,
"(%P|%t) can't register with reactor\n"),
-1);
return 0;
}
/* 명시적으로 close되었다면 "화일 핸들"을 close 할 것이다. 네트워크
처리결과는 클라이언트의 접속을 끊고, reactor에 등록되어있다면 등록을
해제하게 된다. */
int close (void)
{
return this->handle_close (ACE_INVALID_HANDLE,
ACE_Event_Handler::RWE_MASK);
}
/* This is a bit of magic... When we call the accept() method of
the acceptor object, it wants to do work on an ACE_SOCK_Stream.
We have one of those as our connection to the client but it would
be gross to provide a method to access that object. It's much
cooler if the acceptor can just treat the Logging_Handler as an
ACE_SOCK_Stream. Providing this cast operator lets that happen
cleanly. */
operator ACE_SOCK_Stream &()
{
return this->cli_stream_;
}
protected:
/* accept와 마찬가지로 reactor로의 핸들을 제공할 필요가 있다. */
ACE_HANDLE get_handle (void) const
{
return this->cli_stream_.get_handle ();
}
/* 여기에 handle_input()이 있다. 이것이 어플리케이션의 주 작업장소이다. */
virtual int handle_input (ACE_HANDLE)
{
/* 작은 버퍼를 만들고 초기화 한다. 1을 더한 것은 널로 끝나는 문자열에
대한 배려이다. */
char buf[BUFSIZ + 1];
/* ACE_SOCK_Stream의 recv() 매소드를 실행한다. 에러일 경우는 -1을
반환한다. 정상이라면, 읽어들인 바이트 수를 반환한다. 물론, 0바이트
를 읽어들였다면 접속이 끊긴것이다. 어떻게 그것을 알수 있는가?
만약 "조금"이라도 움직임이 없었다면, handle_input() 함수가
reactor에 의해 호출되지 않을 것이고, 끊어진 연결은 reactor로
read 요청처럼 보일 수 있기 때문이다. 그러나 끊어진 연결로부터 read
했을 때에는 항상 0바이트를 읽게 될 것이다.
에러일 경우나 접속이 끊어진 경우라면 -1을 반환하는 것에 주의하라.
그것은 reactor에게 깔끔하게 shutdown할 수 있는 handle_close()
를 호출하도록 알려준다.
이들을 사용하지 않는다 할지라도, recv() 호출에서 사용할 수 있는
몇가지 추가적인 인자들이 있다. 이들중 하나는 ACE_Time_Value 인데
이것은 recv()가 실행되고 블럭당하는 시간을 한정한다. 데이타가
받을 수 있는 상황인 것을 확신할 수 없을 때 이것을 사용할 수 있다.
Since we only get to handle_input() when data is ready, that would be redundant.
On the other hand, if you use recv_n() to read *exactly* a number of bytes then
limiting the time you wait for those bytes might be good.
The other paramter that may come in handy is an integer flags.
This is passed directly to the underlying OS recv() call. */
ssize_t retval;
switch (retval = this->cli_stream_.recv (buf, BUFSIZ))
{
case -1:
ACE_ERROR_RETURN ((LM_ERROR,
"(%P|%t) %p bad read\n",
"client logger"),
-1);
case 0:
ACE_ERROR_RETURN ((LM_ERROR,
"(%P|%t) closing log daemon (fd = %d)\n",
this->get_handle ()),
-1);
default:
buf[retval] = '\0';
ACE_DEBUG ((LM_DEBUG,
"(%P|%t) from client: %s",
buf));
}
return 0;
}
/* handle_input()이 -1을 반환할 때, 여기서 끝낸다. 몇가지 처리할 잡일이 남아있다. */
int handle_close (ACE_HANDLE, ACE_Reactor_Mask _mask)
{
/* reactor로 부터 자기 자신을 제거한다. 반드시 DONT_CALL 마스크를 포함시켜야만 한다!
이것은 handle_close()를 다시 호출하는 것을 막아준다. */
reactor_->remove_handler (this,
_mask | ACE_Event_Handler::DONT_CALL);
/* 클라이언트와 연결되어있는 소켓을 닫는다.*/
cli_stream_.close ();
/* acceptor에 의해 동적으로 할당된 사실을 우리는 알고 있다.
이제 자살할 시간이다. */
delete this;
return 0;
}
protected:
/* 우리의 피어 연결 */
ACE_SOCK_Stream cli_stream_;
/* reactor */
ACE_Reactor *reactor_;
};
#endif /* _CLIENT_HANDLER_H */
주석문이 모든 이야기를 다 해주고 있다. 정말로 흥미있는 부분은 handle_input()이다. 다른 것들은 단지 기본적인 일을 할 뿐이다. 나중에, 기본적인 대부분의 기본작업을 돌보게 되는 ACE_Svc_Handler<>에 대해서 배울 것이다.
7 끝내며 #
자, 이제 ACE를 사용하는데 있어 첫번째 튜토리얼을 종료할 때가 왔다. 우리는 그리 많지 않은 네트워크 프로그래밍을 알지 않고도 간단한 서버를 만드는 방법을 배웠다.
이 튜토리얼의 코드는 설명의 목적으로만 사용되었다. 이것은 코드가 동작할지 안할지 확실하지 않다는 뜻이다. 실제로 이 예제는 동작하지만 치밀한 프로그래머라면 엄청난 양의 내부 메모리 누수가 있는 것을 알 수 있을 것이다. 차후에 수정하도록 하겠다.
수정해야 할 부분이 여러개 있네요~ 그 중에서 하나~^^;
3 Reactor? 에서
'reactor가 이벤트가 무엇때문에 발생했는지에 대해서는 알고있지 않는다는 것에 주의해라.
=> 발생한 특정 이벤트를 처리하기 위해 어떤 일이 일어나는지에 대해서는 책임지지 않는다는 것을 주목하라' 가 맞다는 생각이 드네요~ -- 김병훈 2005-02-17
뒤에 이어서 '그것은 object의 책임이다.reactor는 이벤트가 정확하게 실행될 수 있도록 하는 인식장치에 불과하며, 단순히 이벤트를 객체에 알려줄 뿐이다.'가 좋을 듯 합니다만~ ^^;
역시 제 생각일 뿐~~ -- 김병훈 2005-02-17
예전에 직역한거라 영 안좋은 부분이 있습니다. 언제 한번 다시 리뉴얼해야하는데...
-- redpixel 2005-02-17
-- redpixel 2005-02-17
그래도.. ACE를 시작하는 저에게 너무 좋은 자료인것 같아 매번 감사한 맘을 느끼고 있습니다~~~^^ -- 김병훈 2005-02-17
^^.. 무척 어렵게 느껴지던 ACE가 점점 쉽게 느껴지네요... 감사 드립니다. -- Anonymous 2005-07-11 00:02:46
다좋은데 기사 가로싸이즈를 1024로 좀맞혀주심안될까요... -- 61.74.74.70 2005-09-28









