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

FreePascal 강좌/문서
[8] 라자루스를 이용한 업무 프로그램 개발 - 7
어느좋은날 [freepascal] 564 읽음    2020-06-13 22:43
1-5. 서버 프로그램 procedure

클라이언트로 전송하는 함수는 오류와 같은 문자열과 XML 데이터를 TIdBytes에 넣어서 보내는 두가지 형태가 있다.
// Bytes 전송
procedure TdmnSIMLaz.UP_SIMLazSendB(ACnxt: TIdContext; ACmd: UInt8; var ACont: TIdBytes);
var
  l_Len: Int64;
  l_Data: TIdBytes;
begin
  GP_SIMLazEnc(ACont);
  l_Data := nil;
  l_Len := 1 + Length(ACont);
  AppendBytes(l_Data, ToBytes(l_Len));
  AppendByte(l_Data, ACmd);
  if l_Len > 1 then AppendBytes(l_Data, ACont);
  ACnxt.Connection.IOHandler.Write(l_Data);
end;

// string 전송
procedure TdmnSIMLaz.UP_SIMLazSendS(ACnxt: TIdContext; ACmd: UInt8; ACont: string);
var
  l_Tmp: TIdBytes;
begin
  l_Tmp := nil;
  AppendString(l_Tmp, ACont, -1, u_Encoding, u_Encoding);
  UP_SIMLazSendB(ACnxt, ACmd, l_Tmp);
end;


로그 정보는 앞에 현재 일시를 붙여서 기록한다.
// 로그 처리
procedure TdmnSIMLaz.UP_SIMLazLog(ALog: string; ACont: string);
var
  s: string;
begin
  s := FormatDateTime('[ yyyy-mm-dd hh:nn:ss ] ', Now) + ALog;
  if ACont <> '' then s := s + #13#10 + ACont;
  u_Log.AppendLog(s);
end;


버전 정보는 수신받은 프로그램ID에 해당되는 버전 정보를 DB에서 가져와 문자열로 변환해서 전송한다.
// 버전 정보
procedure TdmnSIMLaz.UP_SIMLazVer(ACnxt: TIdContext; var ACont: TIdBytes);
var
  l_Ver, l_ID: string;
  l_SIMLazDB: TSIMLazDB;
begin
  l_SIMLazDB := TSIMLazDB(ACnxt.Data);
  try
    l_SIMLazDB.Open;
    l_ID := BytesToString(ACont, u_Encoding, u_Encoding);

    with l_SIMLazDB.Query do
    begin
      SQL.Text := 'SELECT ver_major, ver_minor FROM slverm WHERE ver_id = ' + QuotedStr(l_ID);
      Open;
      if Eof then l_Ver := '0.0'
      else l_Ver := IntToStr(FieldByName('ver_major').AsInteger) + '.' + IntToStr(FieldByName('ver_minor').AsInteger);
      Close;
    end;

// Test Mode
if u_TestMode then UP_SIMLazLog(l_SIMLazDB.ConnID + 'Test - ' + l_ID + ' Version = ' + l_Ver);

    UP_SIMLazSendS(ACnxt, smlc_VERSION, l_Ver);
  except
    on E: Exception do
    begin
      if l_SIMLazDB.Query.Active then l_SIMLazDB.Query.Close;
      UP_SIMLazLog(ACnxt.Connection.Socket.Binding.PeerIP, E.Message);
      UP_SIMLazSendS(ACnxt, smlc_ERROR, E.Message);
    end;
  end;
end;


버전 유지를 위한 파일은 서버의 실행파일이 있는 폴더 아래 "files" 폴더에 프로그램ID와 같은 이름의 zip 파일 형태로 있어야한다.
// 파일 가져오기
procedure TdmnSIMLaz.UP_SIMLazFile(ACnxt: TIdContext; var ACont: TIdBytes);
var
  l_Data: TIdBytes;
  l_FileName: string;
  l_Stream: TFileStream;
begin
  l_FileName := ExtractFilePath(ParamStr(0)) + 'files\' + BytesToString(ACont, u_Encoding, u_Encoding) + '.zip';

// Test Mode
if u_TestMode then UP_SIMLazLog('Test - FileName = ' + l_FileName);

  if not FileExists(l_FileName) then
  begin
    UP_SIMLazSendS(ACnxt, smlc_ERROR, ERR_SIMLAZ201);
    Exit;
  end;

  l_Stream := TFileStream.Create(l_FileName, fmOpenRead);
  try
    l_Data := nil;
    l_Stream.Position := 0;
    ReadTIdBytesFromStream(l_Stream, l_Data, l_Stream.Size);
    UP_SIMLazSendB(ACnxt, smlc_GETFILE, l_Data);
    FreeAndNil(l_Stream);
  except
    on E: Exception do
    begin
      FreeAndNil(l_Stream);
      UP_SIMLazLog(ACnxt.Connection.Socket.Binding.PeerIP, E.Message);
      UP_SIMLazSendS(ACnxt, smlc_ERROR, E.Message);
    end;
  end;
end;


로그인 처리는 클라이언트로부터 #9(tab)로 구분된 사용자 ID와 암호를 넘겨받아 사용자 정보 테이블에서 사용자 정보를 확인한 후 관리자 여부와 사용자 명을 클라이언트로 반환한다. 정상적인 정보인 경우 "접속ID"에 "사용자ID"를 추가한다.
// 로그인
procedure TdmnSIMLaz.UP_SIMLazLogin(ACnxt: TIdContext; var ACont: TIdBytes);

  function LF_GetStr(var AValue: string): string;
  var
    i: Int32;
  begin
    if AValue = '' then Result := ''
    else
    begin
      i := Pos(SIMLAZ_EOF, AValue);
      if i = 0 then
      begin
        Result := AValue;
        AValue := '';
      end
      else
      begin
        Result := Copy(AValue, 1, i - 1);
        Delete(AValue, 1, i);
      end;
    end;
  end;

var
  l_OK: Boolean;
  l_SIMLazDB: TSIMLazDB;
  l_UsrID, l_Passwd, l_ConnID, l_Data: string;
begin
  l_OK := False;
  l_SIMLazDB := TSIMLazDB(ACnxt.Data);
  try
    l_Passwd := BytesToString(ACont, u_Encoding, u_Encoding);
    l_UsrID := LF_GetStr(l_Passwd);

    l_SIMLazDB.Open;
    l_SIMLazDB.Query.SQL.Text := 'SELECT usr_name, passwd, admin_yn, use_yn FROM slusrm WHERE usr_id = ' + QuotedStr(l_UsrID);
    l_SIMLazDB.Query.Open;
    if l_SIMLazDB.Query.Eof then l_Data := ERR_SIMLAZ001
    else if l_SIMLazDB.Query.FieldByName('use_yn').AsString <> '1' then l_Data := ERR_SIMLAZ101
    else if l_SIMLazDB.Query.FieldByName('passwd').AsString <> l_Passwd then l_Data := ERR_SIMLAZ102
    else
    begin
      l_ConnID := l_SIMLazDB.ConnID + '_' + l_UsrID ;
      l_SIMLazDB.UserID := l_UsrID;
      l_SIMLazDB.ConnID := l_ConnID;
      l_Data := l_SIMLazDB.Query.FieldByName('admin_yn').AsString + SIMLAZ_EOF + l_SIMLazDB.Query.FieldByName('usr_name').AsString;
      l_OK := True;
    end;
    l_SIMLazDB.Query.Close;

    if not l_OK then // Error
    begin
      UP_SIMLazLog(l_SIMLazDB.ConnID, l_Data);
      UP_SIMLazSendS(ACnxt, smlc_ERROR, l_Data);
    end
    else
    begin
      UP_SIMLazLog(l_SIMLazDB.ConnID + ' login');
      UP_SIMLazSendS(ACnxt, smlc_LOGIN, l_Data);
    end;
  except
    on E: Exception do
    begin
      if l_SIMLazDB.Query.Active then l_SIMLazDB.Query.Close;
      UP_SIMLazLog(ACnxt.Connection.Socket.Binding.PeerIP, E.Message);
      UP_SIMLazSendS(ACnxt, smlc_ERROR, E.Message);
    end;
  end;
end;


데이터 조작을 위한 DML(INSERT/DELETE/UPDATE 등등)은 실행한 후 영향을 받은 레코드 수를 되돌려준다.
// Query 실행
procedure TdmnSIMLaz.UP_SIMLazExec(ACnxt: TIdContext; var ACont: TIdBytes);
var
  l_Cnt: Int32;
  l_SIMLazDB: TSIMLazDB;
begin
  l_SIMLazDB := TSIMLazDB(ACnxt.Data);
  try
    l_SIMLazDB.Open;
    l_SIMLazDB.StartTrans;
    l_SIMLazDB.Query.SQL.Text := BytesToString(ACont, 0, -1, u_Encoding, u_Encoding);

// Test Mode
if u_TestMode then UP_SIMLazLog(l_SIMLazDB.ConnID + 'Test - ExecSQL: ', l_SIMLazDB.Query.SQL.Text);

    l_SIMLazDB.Query.ExecSQL;
    l_Cnt := l_SIMLazDB.Query.RowsAffected;
    l_SIMLazDB.Commit;

    UP_SIMLazSendS(ACnxt, smlc_EXECQUERY, IntToStr(l_Cnt));
  except
    on E: Exception do
    begin
      l_SIMLazDB.Rollback;;
      UP_SIMLazLog(ACnxt.Connection.Socket.Binding.PeerIP, E.Message);
      UP_SIMLazSendS(ACnxt, smlc_ERROR, E.Message);
    end;
  end;
end;


DB 서버에서 조회된 데이터는 "TCustomBufDataset"에서 사용가능한 XML 형태로 변환해서 클라이언트로 보낸다. XML 변환에는 "xml_data2stream.pas"에 있는 "TXMLStreamExporter" 객체를 사용한다. Query 실행 및 데이터 가져오기 시 DB Connection이 끊어져 있을 경우 재접속을 하지만 처리가 끝난후 DB Connection을 임의로 닫지는 않는다. DB와 관련된 처리가 빈번하게 일어날 경우 Connection을 붙이고 끊는 시간으로 인해 느려질 수 있기때문이다.
// 데이터 가져오기
procedure TdmnSIMLaz.UP_SIMLazOpen(ACnxt: TIdContext; var ACont: TIdBytes);
var
  l_Data: TIdBytes;
  l_SIMLazDB: TSIMLazDB;
  l_XML: TXMLStreamExporter;
begin
  l_SIMLazDB := TSIMLazDB(ACnxt.Data);
  l_XML := TXMLStreamExporter.Create(Self);
  try
    l_SIMLazDB.Open;;
    l_SIMLazDB.Query.SQL.Text := BytesToString(ACont, u_Encoding, u_Encoding);

// Test Mode
if u_TestMode then UP_SIMLazLog(l_SIMLazDB.ConnID + 'Test - OpenSQL: ', l_SIMLazDB.Query.SQL.Text);

    l_SIMLazDB.Query.Open;
    l_XML.Dataset := l_SIMLazDB.Query;
    l_XML.Execute;
    l_SIMLazDB.Query.Close;

    l_Data := nil;
    l_XML.Stream.Position := 0;
    ReadTIdBytesFromStream(l_XML.Stream, l_Data, l_XML.Stream.Size);
    UP_SIMLazSendB(ACnxt, smlc_OPENQUERY, l_Data);

    FreeAndNil(l_XML);
  except
    on E: Exception do
    begin
      if l_SIMLazDB.Query.Active then l_SIMLazDB.Query.Close;
      if Assigned(l_XML) then FreeAndNil(l_XML);

      UP_SIMLazLog(ACnxt.Connection.Socket.Binding.PeerIP, E.Message);
      UP_SIMLazSendS(ACnxt, smlc_ERROR, E.Message);
    end;
  end;
end;


사용자 목록 요청은 관리 목적으로, 현재 접속된 클라이언트의 정보를 보여준다. 접속자의 "사용자ID"와 "접속ID", "접속일시"를 조회할 수 있으며 "접속ID"에는 클라이언트의 IP가 포함되어있다.
// 사용자 목록
procedure TdmnSIMLaz.UP_SIMLazUsrLst(ACnxt: TIdContext);
var
  i: Int32;
  s: string;
  l_Lst: TList;
  l_Data: TIdBytes;
  l_SIMLazDB: TSIMLazDB;
begin
  s := '';
  l_Data := nil;
  l_Lst := IdTCPSvr.Contexts.LockList;
  try
    for i := 0 to l_Lst.Count - 1 do
    begin
      l_SIMLazDB := TSIMLazDB(TIdContext(l_Lst.Items[i]).Data);
      s := l_SIMLazDB.UserID + SIMLAZ_EOF +
        l_SIMLazDB.ConnID + SIMLAZ_EOF +
        FormatDateTime('yyyy-mm-dd hh:nn:ss', l_SIMLazDB.ConnTime) + #13#10;
      AppendString(l_Data, s, -1, u_Encoding, u_Encoding);
    end;
  finally
    IdTCPSvr.Contexts.UnlockList;
  end;
  UP_SIMLazSendB(ACnxt, smlc_USERLIST, l_Data);
end;


하나의 procedure를 이용해서 일정 시간마다 요청시간이 지정된 시간을 초과한 클라이언트의 접속을 끊거나, 서비스 종료시 전체 클라이언트의 접속을 끊는다. AAll 파라메터가 True면 전체 클라이언트를, False이면 시간이 초과된 클라이언트의 접속을 종료시킨다.
// 클라이언트 제거
procedure TdmnSIMLaz.UP_SIMLazKill(AAll: Boolean);
var
  i: Int32;
  l_Lst: TList;
  l_Now: TDateTime;
begin
  l_Now := Now;
  l_Lst := IdTCPSvr.Contexts.LockList;
  try
    for i := 0 to l_Lst.Count - 1 do
      if AAll or (MinutesBetween(TSIMLazDB(TIdContext(l_Lst.Items[i]).Data).ReqTime, l_Now) >= u_KillTime) then
        TIdContext(l_Lst.Items[i]).Connection.Disconnect;
  finally
    IdTCPSvr.Contexts.UnlockList;
  end;
end;


완성된 프로그램을 컴파일 한 후 관리자 모드의 명령 프롬프트에서 "/install" 파라메터를 붙여서 실행하면 서비스에 등록된다.
simlaz_server.exe /install

서비스에서 제거 하려면 관리자 모드의 명령 프롬프트에서 "/uninstall" 파라메터를 붙여서 실행하면 된다.
simlaz_server.exe /uninstall

+ -

관련 글 리스트
8 라자루스를 이용한 업무 프로그램 개발 - 7 어느좋은날 564 2020/06/13
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.