Q&A

  • 이중철님! Indy UDP서버의 수신처리에 관한 코드를 잠깐만 보아주세요.
안녕하세요?

지난번 UDP서버에 5000명 정도의 동시접속이 있을때, 수신되는 데이타처리에 대한 질문을 했었구요, 님이 답변을 주시기 전까지는 답답한 심정이었었는데, 명확하게 방향을 잡는 답변을 주셔서 많이 감사했습니다.

말씀하신대로 TList를 이용해서 수신처리를 해 보았지만 제가 할 수 있는 방법은 new, dispose의 사용, 검색도 순차검색으로 밖에 처리가 안되었습니다. 저의 한계인가봅니다. 이것으로도 되긴 하겠지만 속도가 문제가 되더군요. 그래서 님께서 말씀하신 new를 사용하지 않고 메모리풀을 이용하는 방법과 그리고 검색하는데 있어서도 순차검색이 아닌 트리구조를 이용해 검색을 하려면 아래코드를 어떻게 변경을 해야할지 방향만이라도 간략하게 말씀해 주시면 감사하겠습니다.

잠깐 밑의 코드(UDP서버의 Read이벤트)의 상황을 말씀드리면,

클라이언트에서 보내는 데이타는 ID(이메일 ID와 동일)이구요, 20초에 1번씩 서버로 보냅니다.
이렇게 동시에 전송하는 클라이언트 수는 5000명 정도 되구요.

TList에 저장되는 데이타는 고객ID와 수신시점의 날짜및시간 입니다.
즉, 수신되는 ID가 TList에 없다면 ID와 현시점의 날짜와시간을 TList에 추가하고 있구요,
만약 TList에 ID가 존재한다면 날짜와시간만 현재일시로 업데이트를 해주고 있습니다.

그리고 DB로의 저장은 쓰레드를 이용해 5초 간격으로 하려고 하고 있습니다. (DB저장은 아래코드에 포함 안되었습니다.)



type
  TRecvData = Record                
    UserId : String[15];            //고객 ID
    RecvTime : String[20];          //수신시점의 날짜와시간
  end;
......
var
  RecvDataList : TList;
......



procedure TUDPMainForm.UDPServerUDPRead(Sender: TObject; AData: TStream; ABinding: TIdSocketHandle);
var
  pRecvData : ^TRecvData;  //TRecvData를 가리키는 포인터 선언
  DataStringStream: TStringStream;
  i,Count:integer;
  NameCompare : String;


begin
  DataStringStream := TStringStream.Create('');
  try
    DataStringStream.CopyFrom(AData, AData.Size);
    NameCompare := DataStringStream.DataString;  //수신 데이타
    i := 0;
    Count := 0;


    // 이미 수신된 ID가 있는지 검색
    for i := 0 to RecvDataList.Count - 1 do
    begin
      if NameCompare = TRecvData(RecvDataList.Items[i]^).UserId then  //이미 수신된 ID가 존재한다면
        begin
          // 현재날짜 및 시간으로 업데이트
          TRecvData(RecvDataList.Items[i]^).RecvTime:=FormatDateTime('YYYY-MM-DD hh:nn:ss', Now);
          Count := 1;
          Break;
        end
      else
        begin
          Count := 0;
        end;
    end;


    // ID가 없다면 TList에 추가
    if Count=0 then begin
      New(pRecvData);                              
      pRecvData^.UserId:=DataStringStream.DataString;
      pRecvData^.RecvTime:=FormatDateTime('YYYY-MM-DD hh:nn:ss', Now);
      RecvDataList.Add(pRecvData); // TList에 추가
    end;


  finally
    DataStringStream.Free;
  end;


End;
3  COMMENTS
  • Profile
    이중철 2005.10.01 19:56
    첫째 RecvTime 입니다.
    이것은 그냥 Double(또는 TDateTime)로 정의하여 주세요
    장점은 메모리활용이 더 좋고 매번 FormatDateTime을 콜해서 String을 변환하는
    Overload가 없다는 것 입니다.

    둘째는 Function내 Local 변수는 최대로 줄이세요
    방법은 세번째를 설명하면서 자연적으로 적용 됩니다.

    세쩨는 ShortString의 특성을 이해하고 이것을 한껏 이용하세요
    ShortString은 첫번째바이트는 스트링의 길이정보가 있으며 나머지는 스트링입니다.

    자 그럼 이와같이 변경되겠죠

    type
      TRecvData = Record                
        UserId : String[15];            //고객 ID
        RecvTime : double;             //수신시점의 날짜와시간
      end;

    var
      RecvDataList : TList;

    procedure TUDPMainForm.UDPServerUDPRead(Sender: TObject; AData: TStream; ABinding: TIdSocketHandle);
    var
      pRecvData : ^TRecvData;  //TRecvData를 가리키는 포인터 선언  
      i : integer;
    begin
      new(pRecvData);
      byte(pRecvData^.UserID[0]) := AData.Size;
      AData.read(pRecvData^.UserID[1], AData.Size);
      for i := 0 to RecvDataList.Count - 1 do
        if pRecvData^.userId = TRecvData(RecvDataList.Items[i]^).UserId then  
        begin
            dispose(pRecvData);
            TRecvData(RecvDataList.Items[i]^).RecvTime := now;
            exit;
        end;
      
       RecvDataList.Add(pRecvData);


    End;

    트리구조와 메모리풀은 나중에요


  • Profile
    이중철 2005.10.01 20:58
    먼저 해당 프로젝트를 분석이 안된상황에는 이것이 꼭 필요하다고 말씀 못드립니다.
    그러나 가정해 볼 수 있는 상황으로 두가지로 나누어 진다고 보여 집니다.
    첫째는 해당 유저들은 Database에 기 등록된 유저이다.
    둘째는 해당 유저들은 Database에 등록되지 않았으며 전혀 알지 못하는 유저들이다.

    일반적인 프로젝트에서는 첫번째의 경우가 90% 이상입니다.
    이경우에는 DB에서 해당 유저들을 모두 가져오고 메모리에 저장해둡니다.
    만약 DB일 경우에는 유저ID로 Sort하여 가져 옵니다.
    DB가 아닐경우에는 유저ID로 sort하는 프로그램을 사용합니다.
      - 소트하는 방법은 TList의 Sort를 사용하는 방법과
      - 소트하는 프로그램을 만드는 방법이 있습니다.
    메모리 활용 및 속도가 가장 빠른 메모리 저장방법은 TList보다 배열입니다.
    제 경우 TList를 사용하는 이유는 다음과 같습니다.
      저장되어 있는 데이터가 DB가 아니고 소트하는 프로그램을 작성하기 귀찮을때 사용 합니다.
    이제 배열로 데이터들이 저장되었다고 가정 하겠습니다.
    데이터의 구조는 다음과 같습니다.
    type
      TRecvData = Record                
        UserId : String[15];            //고객 ID
        RecvTime : double;             //수신시점의 날짜와시간
        IsAlive : boolean;
      end;
    이제 가장 중요한 Binary Search Function을 만듭니다.

    장점은 속도가 가장 빠르다. (이보다 빠르게 접근하기 어렵습니다.)
    단점은 신규ID 등록시 대응 하기가 어렵습니다.
    (다시 배열을 잡고, 소트하고 해야 하므로 등록과 삭제가 빈번할 시에는 주기적(약 5분, 10분등)으로 재작성)

    두번째의 경우는 일반적인 상황은 아닙니다.
    그러나.. 가끔은 필요할 경우가 있는데 Tree 구조 자체의 Overload등 이 있어서 어느정도 숫자(100개미만?)이하에서는 쓰지 않는것이 좋습니다.
    Tree 구조는 그 특성상 한쪽으로 편중되는 경우가 있습니다. 이것으로 뭐라고 하는데 까먹었네요 ^^
    제가 썼던 방식은 AVL Tree 입니다. 이것은 높이차를 비교해서 치우침을 방지하는 Tree입니다.
    20년전에 배웠던 자료구조론의 AVL Tree의 내용이 있어서 구현하여 썼습니다.
    (그런데 20일전에 노트북을 아리랑치기에 당해서 ㅠㅠ 소스 부재)
    데이터의 기본구조는 다음과 같습니다.
    type
      TRecvData = Record                
        UserId : String[15];            //고객 ID
        RecvTime : double;             //수신시점의 날짜와시간
        Left : Pointer;
        Rigth : Pointer;
      end;
    % 자료구조론 책에서 파스칼로 되어 있습니다.

    마지막으로 메모리풀 입니다.
    메모리풀의 사용목적도 속도향상과 메모리활용에 있습니다.
    메모리활용쪽으로 보시면 TList를 그냥 사용시 일반적으로
    New(GetMem, 객체의 Create포함)를 사용하는데 이때 메모리 할당을 받으면 단 1Byte짜리 자료를 할당을 받더라도 16바이트(음 16인지 24인지 ㅠㅠ) Overhead(Owner Handle, Address등등)가 포함되어 있습니다.
    작은 자료(1~10Byte)를 1000개 10,000개 생성시에는 문제가 많죠 배보다 배꼽이 커지니..
    속도면에서도 new, dispose를 반복하여 Function call하니 당연히 별로 안좋죠..
    제가 소스를 분실해서 예제를 못 달겠네요
    메모리풀은 여기 게시판이나 조금 생각해보면 어떻게 사용하면 되겠다 .. 그런 생각이 들거에요
    일단  new와 dispose를 최대한 줄이는 것과 순차검색을 최대한 막는 것을 목표로 구상해 보세요

    그럼 이만..

  • Profile
    고영호 2005.10.01 23:11
    안녕하세요 이중철님!

    시간을 내어 주셔서 자세하게 답변을 해 주셔서 너무나 감사드립니다.

    늘 건강하세요.