C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[859] 인디9 TCPServer에서 패킷이 들어온 만큼 바로 받기.
김태선 [cppbuilder] 11631 읽음    2009-02-20 20:03
인디9의 TIdTCPServer 컴포넌트는 매우 막강한 서버를 만들 수 있는 컴포넌트입니다.
크기도 가볍고 부드럽고 깨끗하게 동작합니다.
단지, 쓰레드 블럭킹 방식을 쓰기 때문에, IOCP 처럼 수천~1만 커넥션 이상을 처리하기는 좀 어렵다는 것입니다.
그렇지만 대부분의 서버용으로는 적합하고, 이는 수 많은 빌더/델파이 플머들이 이미 검증한
내용이기도 합니다.

보통 인디 서버인 TIdTCPServer 가 실행되고 난뒤 접속이 들어오면 처리 쓰레드가 생성되고
쓰레드는 Execute 메소드로 루프를 돌며 서버 작업을 할수 있도록 하는데
대략은 아래 같은 구조를 가지게 됩니다.
이건 필자가 쓰는 방식이라 꼭 정답은 아닙니다.
그냥 참고 차원에서 올리는 것입니다.

//---------------------------------------------------------------------------

void __fastcall TFormServerMain::TCPServerExecute(
      TIdPeerThread *AThread)
{
    String  PeerIP = AThread->Connection->Socket->Binding->PeerIP;
   
    const int READBUF_SIZE = 8300;
    byte  *readbuf = (byte *)new byte[READBUF_SIZE];
    while(!AThread->Terminated)
    {
        THead    *head = (THead *)readbuf;
        try
        {
            AThread->Connection->ReadBuffer(head, sizeof(THead));

            ; // 여기서 들어온 패킷을 처리한다.
        }
        catch(EIdReadTimeout& e)
        {
            add("Read Timeout : " + PeerIP);
            break;
        }
        catch(EIdClosedSocket& e)
        {
            // 이는 정상적인 종료일 경우다.
            add("Closed Socket : " + e.Message + " : " + PeerIP);
            break;
        }
        catch(EIdException& e)
        {
            if (e.Message.Pos("racefully") <= 0)     // Gracefully Closed 는 제외한다.
            {
                add("id Exception : " + e.Message + " , " + PeerIP);
            }
            break;
        }
        catch(Exception& e)
        {
            add("Exception : " + e.Message + " , " + PeerIP);
            break;
        }
        catch(...)
        {
            add("etc Exception : " + PeerIP);
            break;
        }
    }
    delete[] readbuf;
   
    ; // 뒷처리 할게 있으면..
}
//---------------------------------------------------------------------------


대략 이런 식으로 구성되는데
ReadBuffer(...) 에서 원하는 길이 만큼의 데이타가 들어올때까지 블럭킹하고 있다가
들어오면 함수가 리턴되어 돌아옵니다.

이렇게 구성하는 경우는 패킷 길이를 정확하게 아는 경우 입니다.
만일 패킷 길이가 일정하지 않다면 어떻게 할까요?
보통은
ReadString 같은 문자열을 읽은 메소드를 써서 가져오면 됩니다.
인디는 이를 위해 여러가지 메소드가 잘 준비되어 있습니다.
이는 다시 말해 문자열로 프로토콜을 구성하면 쉽게 통신 규약과 처리를 할수 있다는 뜻이 되기도 합니다.
그런데 만일 패킷이 문자열 형태가 아니라 바이너리 형태라면 어떻게 해야 할까요?
물론 처음 예를 보인 ReadBuffer 를 써서 읽어야 하는데,
문제는 바이너리 이면서 패킷 길이도 일정하지 않는 경우는 어떻게 읽으면 될까요?
이건 어려운 문제가 아닌데 볼포.델마당 모두 질문은 있어도 답은 없길래
간단히 테스트한 뒤 올려 봅니다.

바이너리이던지 문자열 패킷이던지 간에 패킷이 들어오는 즉시 들어온 만큼 바로 처리하고 싶으면
ReadBuffer 대신
int  readsize = AThread->Connection->Socket->Recv(readbuf, READBUF_SIZE);
를 사용하면 됩니다.
그러면 readsize 에는 실지로 읽은 크기 만큼 패킷이 들어오게 됩니다.
즉 패킷이 없으면 블럭킹하고 있다가 한번에 패킷이 들어오는 양만큼 즉시 바로 리턴되어 들어오기 때문에
바로 바로 처리할 수 있게 됩니다. 가령 telnet 으로 접속하면 telnet 터미널 특성상
키보드 입력하는 즉시 보내므로 1~2바이트 짜리 패킷이 주르르 쏟아져 들어오게 됩니다.



여기서 하나, TCP 통신 프로그램을 만들때 주지하고 있어야 할 사항은
패킷은 언제 어떤 크기 만큼 들어올지 본질적으로 정해져 있지 않다는 것입니다.
보내는 쪽에서는 1000 바이트를 한방에 보내도 네트웍이 원할하지 않으면
이것이 400바이트 600바이트 식으로 두번에 걸쳐서 올수도 있고 아니면
네트웍이 매우 느리면 10바이트 20바이트 10바이트 식으로 들어올 수도 있습니다.
단지, TCP 에서 보장하는 것은 패킷이 보내는 순서대로 들어온다는 순차성과
패킷이 깨지지 않게 스스로 검증하기 때문에 들어온 패킷이 이상이 없는지 별도의 검사를 하지 않아도 된다는 것입니다.

물론 해킹에 의한 변조가 있을 수 있으므로 이를 검사하기 위한 코드를 넣어야 한다면
그건 매우 바람직한 일입니다.

그러므로 일정한 규칙의 프로토콜 구성하여
ReadBuffer 나 ReadString 등 인디가 제공하는 메소드를 쓰게 되면
매우 간편하게 이러한 문제를 벗어날 수 있습니다.
즉 로우 소켓인 win32 api 만을 써서 프로그래밍하는 것에 비해 월등히 편리하게 되는 것입니다.

보통 이와 같이 한번에 들어오는 패킷 사이즈를 보증하지 않는 TCP 특성 때문에,
로우 소켓을 이용할 때는 Queue 메모리 영역을 할당 받아 여기에 패킷을 축적하다가 원하는 사이즈가 되면
처리하는 방법을 쓰는데, 이는 이를 위해 별도의 큐 관리 코드가 들어가야 하는 문제가 있습니다.
즉 win32 api 만을 써서 프로그래밍 할때 안정적인 패킷의 처리를 위해서는 큐메모리의 유지가
필수라는 것입니다. 이것은 그다지 어려운 기술도 아니므로 별 문제가 안될 수도 있으나,
인디가 단순히 ReadBuffer 나 ReadString 등의 패킷을 읽어 들이는 메소드를 쓰면
아무런 신경쓰지 않고 해결될 수 있는 문제를 한 참의 디버깅을 거친 뒤 몇 시간을 낭비해 가며
만들어야 하는 로우 소켓 프로그래밍에 비해 인디는 매우 편리하다는 것이죠.

Win32 API의 로우 소켓 뿐만 아니라 빌더에서 제공하는 흔히 기본소켓이라 부르는
TServerSocket TClientSocket 도 마찬가지입니다. 반드시 패킷이 잘려져서 오는 현상에 대한
대비를 해야 합니다. 그러나 성능이 그다지 좋지 않으니 그냥 인디를 쓰시는게 건강에 좋습니다.


그럼..
조대현.Clau [casanebula]   2009-07-07 11:07 X
말씀하신 내용과 try/catch 구문 많은 도움이 되었습니다. 감사합니다^^
김태선 [cppbuilder]   2011-01-01 22:34 X
만일 읽을때 Timeout 까지 적용하려면
다음과 같이 하면 됩니다.
가령 1초 안에 못 읽으면 timeout 나는 것으로 한다면

            int readsize = TCPClient->ReadFromStack(false, 1000, false);
            if (readsize > 0)
            {
                TCPECClient->ReadBuffer(buf, readsize);

만일 timeout 나면 readsize는 -1 값을 가집니다.

이 코드가 실무에는 나을 것입니다.


+ -

관련 글 리스트
859 인디9 TCPServer에서 패킷이 들어온 만큼 바로 받기. 김태선 11631 2009/02/20
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.