Server
 * recv_n에 10초간 타임아웃을 주고 대기함
        char    buff[1024];
        size_t  recvLen = 0;
        ACE_Time_Value  timeout(10);
        int ret = peer.recv_n( buff, sizeof(buff)-1, &timeout, &recvLen );
        ACE_DEBUG( (LM_DEBUG,
                ACE_TEXT("ret[%d] recvLen[%d] : [%.*C]\n"),
                ret, recvLen, recvLen, buff) );
Client
 * 접속하자 마자 첫 메세지("Hello ")를 보내고(send_n)
 * 5초후 (sleep 5초) 나머지 데이터("World!")를 보내고 접속을 끊는다.
        peer.send_n( "Hello ", 6 );

        ACE_OS::sleep( 5 );
        peer.send_n( "World!" , 6 );
        peer.close();
 * 결과 :
 *  recv_n는 첫 메세지를 받고 timeout(10초)동안 블럭이 걸려있었으며
 *  5초후 나머지 데이터를 받고 0을 반환후에 종료하였습니다.
 *  받은 데이터 사이즈는 recv_n 4번째 인자로 받습니다.
$ ./recv
ret[0] recvLen[12] : [Hello World!]
서버 소스 Recv.C
/*
 * 10초간 recv를 걸어놓고 받는것을 테스트함.
 *
 * 상대방에서는(send.C) 접속하자 마자 첫 메세지를 보내고
 * 5초간 sleep를 준다음 나머지 데이터를 보내고 접속을 끊는다.
 *
 * 결과 :
 *  recv_n는 첫 메세지를 받고 timeout(10초)동안 블럭이 걸려있었으며
 *  5초후 나머지 데이터를 받고 0을 반환후에 종료하였습니다.
 *  받은 데이터 사이즈는 recv_n 4번째 인자로 받습니다.
 */
#include <ace/SOCK_Stream.h>
#include <ace/INET_Addr.h>
#include <ace/SOCK_Acceptor.h>
#include <ace/Time_Value.h>
#include <ace/Log_Msg.h>

int ACE_TMAIN( int, ACE_TCHAR * [] )
{
        ACE_INET_Addr   myaddr(9090);

        ACE_SOCK_Acceptor       acceptor;

        if( acceptor.open( myaddr ) == -1 )
        {
                ACE_ERROR_RETURN( (LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("acceptor.open()")), 1 );
        }

        ACE_OS::sleep(5);
        ACE_SOCK_Stream peer;
        if( acceptor.accept( peer ) == -1 )
        {
                ACE_ERROR_RETURN( (LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("acceptor.accept()")), 1 );
        }

        char    buff[1024];
        size_t  recvLen = 0;
        ACE_Time_Value  timeout(10);
        int ret = peer.recv_n( buff, sizeof(buff)-1, &timeout, &recvLen );
        if( ret == -1 )
        {
                ACE_ERROR_RETURN( (LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("recv_n()")), 1 );
        }

        ACE_DEBUG( (LM_DEBUG, ACE_TEXT("ret[%d] recvLen[%d] : [%.*C]\n"), ret, recvLen, recvLen, buff) );
        peer.close();

        return 0;
}
클라이언트 소스
send.C
/*
 * 서버에 접속하자 마자 첫 메세지("Hello ")를 보내고
 * 5초간 sleep를 준다음 나머지 데이터( "World!\n")를 보내고 접속을 끊는다.
 */

#include <ace/SOCK_Stream.h>
#include <ace/INET_Addr.h>
#include <ace/SOCK_Connector.h>
#include <ace/Time_Value.h>
#include <ace/Log_Msg.h>
#include <ace/OS.h>

int ACE_TMAIN( int, ACE_TCHAR * [] )
{
        ACE_INET_Addr   svrAddr(9090, ACE_TEXT("127.0.0.1"));

        ACE_SOCK_Connector      conn;

        ACE_SOCK_Stream peer;
        if( conn.connect( peer, svrAddr ) == -1 )
        {
                ACE_ERROR_RETURN( (LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("connect()")), 1 );
        }

        char    buff[1024];
        ACE_OS::strcpy( buff, "Hello " );
        if( peer.send_n( buff, ACE_OS::strlen(buff) ) == -1 )
        {
                ACE_ERROR_RETURN( (LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("send_n() 2")), 1 );
        }

        ACE_OS::sleep( 5 );
        ACE_OS::strcpy( buff, "World!" );
        if( peer.send_n( buff, ACE_OS::strlen(buff) ) == -1 )
        {
                ACE_ERROR_RETURN( (LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("send_n() 1")), 1 );
        }

        peer.close();

        return 0;
}

결과
$ ./recv
ret[0] recvLen[12] : [Hello World!]
ACE_OutputCDR를 사용하는데 Big endian 저장이되지 않는 현상을 발견했습니다. 아무리 옵션을 바꿔봐도 되지않아 찾아보니... ACE 컴파일을 다시 해야하더군요.
$ACE_ROOT/ace/config.h 파일에 아래 내용을 추가한후 재컴파일하여 빌드하세요.
#define	ACE_ENABLE_SWAP_ON_WRITE	1	// enable swap byte_order
테스트 코드

#include "ace/cdr_stream.h" #include "ace/log_msg.h" void OutputCDR_Test( ACE_CDR::UShort id, int endian ) { ACE_OutputCDR cdr( sizeof( id ), endian ); cdr << id; ACE_HEX_DUMP( ( LM_DEBUG, cdr.begin()->rd_ptr(), cdr.begin()->length() ) ); } int ACE_TMAIN (int argc, ACE_TCHAR *argv[]) { ACE_DEBUG( ( LM_DEBUG, ACE_TEXT("\nBYTE_ORDER_LITTLE_ENDIAN\n") ) ); OutputCDR_Test( 0x1234, ACE_CDR::BYTE_ORDER_LITTLE_ENDIAN ); ACE_DEBUG( ( LM_DEBUG, ACE_TEXT("\nBYTE_ORDER_BIG_ENDIAN\n") ) ); OutputCDR_Test( 0x1234, ACE_CDR::BYTE_ORDER_BIG_ENDIAN ); return 0; }
결과값
BYTE_ORDER_LITTLE_ENDIAN
HEXDUMP 2 bytes
34 12                                              4.

BYTE_ORDER_BIG_ENDIAN
HEXDUMP 2 bytes
12 34                                              .4
ACE 기반 채팅 서버(Proactor)와 MFC기반 채팅 클라이언트입니다.

이 채팅 프로그램은  

TCP/IP 소켓 프로그래/

한빛미디어

책을 참고 하였음을 알려드립니다

클라이언트 부분은 버그 몇개(심각한 버그 포함)를 수정하여 책을 보고 작성및 소스를 참고 하였으며

서버 부분은 책과는 다르게 프로토콜을 제외한 모든 부분을 새로 작성하였으며
ACE Reactor 기반을 거처 Proactor 기반으로 변경하여 만들었습니다.

실행방법은 압축을 풀면 bin디렉토리에 ACE_ChatServer.exe를 실행시키고
ChatClient.exe 를 실행시키면됩니다.

사용포트는 9090 포트를 이용합니다.


플로우 차트와 설계 문서는 아래를 참고 하세요.

ACE 기반 채팅서버 - 서버 접속

ACE기반 채팅서버 - 대화방생성

ACE 기반 채팅서버 - 대화방 입장

채팅서버 - 대화

채팅서버 - 클라이언트 접속 종료

대기방



대화방



서버



소스및 실행파일


◆ 설명 ◆
이 프로그램은 ACE기반으로 만들어져 있습니다.

이프로그램의 용도는 제가 작성한 자동화 프로그램중
  클라이언트 담당 (파일 압축, 서버에 파일전송)
  그리고 서버 담당 (압축해제, 파일 분배)한 프로그램입니다.

이 프로그램은 보여주기 위한 코드이며 상당부분이 제거 되어 있습니다.

◆실행 방법 ◆
예제를 간단히 하기 위해 파일을 c:\OneTouch에 압축을 풀어 실행하세요.

1. 서버 실행
 서버 프로그램을 서비스에 설치하거나 디버그 모드로 실행합니다.
 서비스 모드 설치 후 윈도우 서비스 모드에서 실행합니다.

 디버그 모드로 실행
  c:\OneTouch\bin\OneTouchServer.exe -d

 서비스 모드로 실행
 c:\OneTouch\bin\OneTouchServer.exe -i

 서버 부분에서는 서버로 파일을 동기화 하는 부분이 빠져있습니다.
 빠진 이유는 전 회사에서 처리하는 소스가 포함되어 있기 때문에 빠져있습니다.

2. 클라이언트 실행
 클라이언트를 더블클릭하여 실행후  Start 버튼 클릭.
  c:\OneTouch\bin\OneTouchClient.exe

 클라이언트 부분에서는  DB에 접속하여 게임에서 쓰는 데이터 를 파일로 만드는 작업,
 유료 아이템정보 암호화 등이 빠져있습니다.

3. 실행결과
 로컬디렉토리(c:\OneTouch\Local\2010_05_05\Patch)를 압축하여
 리모트디렉토리(c:\OneTouch\Remote\2010_05_05\Patch)에 압축을 풀어
 이를 분산 복사 합니다.
 
  설정 파일 config.ini, config_s.ini를 참고 해주세요.
사실 이책을 4번은 봤겠지만 정식으로 재대로 본것은 약 세번째인것 같다..

사실 이렇게 짧은 시간에 한권을 봤다는것이 믿기지가 않는다..

6월 25일부터 불과 5일만에 다 봤다.

놀랍다.

전에 봤을때는 한 한달 이상은 걸린것 같은데 말이다.

아무튼 지금까지 ACE에 109시간을 투자했다.

그런데 문제가 발생했다.

지금까지는 하루 3~5시간중 2시간 이상은 책으로 채웠는데
이제 읽을책이 별로 없다.

이제 실습으로 3~5시간을 채워야하는데... 어떻게하지?

이제 The C++ Network Progrmming Volum2을 다시 읽어야할것 같지만..
이건 본지 별로 안되었다. 좀 흥미가 나지 않을것 같아 걱정이네^^

실습은 메신저 서버, 혹은 ACE 기초 클래스를 구현해 보는걸로 결정했다.

약간은 해맬걸 같지만 ^^ 그래도 10000시간을 해낼테다^^
이 책을 4번째 봤다.
첫번째는 그냥 어렵게 봤고 두, 세번째는 대충 본것 같다.

4번째는 ACE에 필요한 지식을 다시한번 다지기 위해 자세히 재밌게 봤는데.. 역시 좋은책이었다.

tcp/ip 기초 --> IO 멀티 플렉싱(select등) -->  동기화 예제(mutex, semaphore) --> 쓰레드 --> IOCP 순으로 가는
체계적으로 상위 수준으로 올라가는 방향이 참으로 좋았고

TIP/IP 프로그래밍에 필요한 mutex, semaphore, Event 등을 쉬운 예제로 설명해주는것
IOCP를 쓰기위해 사전에 필요한 지식들(쓰레드, 비동기 입출력,  Overlaleed 입출력)을 설명및 쉬운 예제로 보여주는것또한 좋았다.

초, 중급 네트워크 프로그래머에게 상당히 도움이 될 책인것은 분명하며
초급 프로그래머시면 한번만 읽지 마시고 시간을 충분히 가지고 충분한 시간이 지난다음 여러번 읽어보는것 또한 좋을것 같다.

다음은 다시 C++ Network Programming Vaolum 1 ACE와 패턴을 사용한 객체 지향 네트워크 프로그밍 책을 3번째 볼 생각이다.

현재 ACE에 90여시간 투자중이다.^^ 앞으로의 계획은 ACE(ACE와 ACE를 배우는데 필요한 지식들)에 1만시간을 투자하는것이다.

ACE_wrappers/examples/NT_Service 에 있는 서비스 등록을 참고(배껴서? ㅡ.ㅡ)하여 서버 프로그램을
서비스화 하였다.

그런데 서비스 등록후 시작하지 않는 문제가 발생했습니다.

이상한건 특정 사용자로 로그온하여 실행하면 된다는것입니다.


알고 보니 파일에 권한에 SYSTEM 없었습니다.^^
그래서 SYSTEM 권한을 추가하였습니다.

그런데. 그런데로 안되었습니다 ㅡ.ㅡ
또 알고 보니 실행파일이 있는 곳에 로그 파일을 만드는데.. 로그 파일을 못만들어서 생기는 문제가 있더군요.
해당 디렉토리를 작업 디렉토리로 설정하는것을 까먹었더군요^^


        // 작업 디렉토리 설정.
        char    workdir[128];
        GetModuleFileName( 0, workdir, sizeof workdir  );

        char *    pDir = ::strrchr( workdir, '\\' );
        if( 0  != pDir )
            *pDir = 0;

        ::SetCurrentDirectory( workdir );
마지막으로 해당 디렉토리에 SYSTEM 권한을 주어 해결하였습니다.


ACE_Select_Reacot를 ACE_Reactor::instance 에 적용예제
윈도우에서 ACE책 예제(ACE 프로그래머 가이드, C++ NetworkProgramming Volum1, 2)에 나온 소스를 볼때 Select_Reactor로 적용하는것이 작동을 원할하게 할수 있다.


#include "ace/Select_Reactor.h"
#include "ace/Reactor.h"

int ACE_TMAIN( int , ACE_TCHAR * [] )
{
    ACE_Select_Reactor * ps = new ACE_Select_Reactor;

    // 두번째 인자를 true로 하면 Reactor가 삭제될때 첫번째 인자로 넘긴 Reactor를 자동삭제 된다.
    ACE_Reactor * pr = new ACE_Reactor( ps, true );

    // 두번째 인자를 true로 하면 Reactor가 삭제될때 첫번째 인자로 넘긴 Reactor를 자동삭제 된다.
    ACE_Reactor::instance( pr, true );
    ...
    ...

    ACE_Reactor::instance()->run_reactor_event_loop();

    return 0;
}
Task 구현
class LogTest_Task : public ACE_Task<ACE_NULL_SYNCH>
{
	typedef ACE_Task<ACE_NULL_SYNCH>	super;
private:
	ACE_Log_Msg_Callback * m_pLogCallback;

public:
	LogTest_Task( ACE_Log_Msg_Callback * logCallback )	:
		super(),
		m_pLogCallback( logCallback )
	{
	}

	virtual int		svc()
	{
		if( m_pLogCallback )
		{
			ACE_LOG_MSG->set_flags( ACE_Log_Msg::MSG_CALLBACK );
			ACE_LOG_MSG->clr_flags( ACE_Log_Msg::STDERR );
			ACE_LOG_MSG->msg_callback( m_pLogCallback );
		}

		// Log 출력.
		ACE_DEBUG( ( LM_DEBUG,
					ACE_TEXT("DEBUG hello World") ) );
		ACE_ERROR( ( LM_ERROR,
					ACE_TEXT("ERROR hello World") ) );	
		return 0;

	}

};


호출
// Task생성및 Task 실행.
LogTest_Task	logTask( ACE_Log_Msg::instance()->msg_callback() );
logTask.activate();
logTask.wait();
추가된소스
BOOL CACE_MFCApp::InitInstance()
{
        .....

        /////////////////////////////////////////////////////////////////
        // 추가
        WORD                    wVersionRequested;
        WSADATA                 wsaData;
        int             err = 0;

        wVersionRequested = MAKEWORD( 2, 2 );
        if( WSAStartup( wVersionRequested, &wsaData ) != 0 )
                return FALSE;

        ACE::init();
        /////////////////////////////////////////////////////////////////

        CACE_MFCDlg dlg;
        m_pMainWnd = &dlg;
        INT_PTR nResponse = dlg.DoModal();
        if (nResponse == IDOK)
        {
                // TODO: Place code here to handle when the dialog is
                //  dismissed with OK
        }
        else if (nResponse == IDCANCEL)
        {
                // TODO: Place code here to handle when the dialog is
                //  dismissed with Cancel
        }

        /////////////////////////////////////////////////////////////////
        // 추가
        ACE::fini();
        WSACleanup( );
        /////////////////////////////////////////////////////////////////

        // Since the dialog has been closed, return FALSE so that we exit the
        //  application, rather than start the application's message pump.
        return FALSE;
}

+ Recent posts