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

FreePascal 강좌/문서
[10] 라자루스를 이용한 업무 프로그램 개발 - 9
어느좋은날 [freepascal] 548 읽음    2020-06-13 22:45
2-2. DataModule procedure

데이터 전송 전에 전송할 데이터를 암호화하고 전송할 데이터의 길이와 "구분자" 한 자리를 더한 값을 앞에 추가한 후 전송한다.
// 데이터 전송
function TdmSIMLaz.UF_SIMLazSnd(ACmd: UInt8; var ACont: TIdBytes): Boolean;
var
  l_Len: Int64;
  l_Data: TIdBytes;
begin
  Result := False;
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  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);
  IdTCPConn.IOHandler.Write(l_Data);
  Result := True;
end;


데이터 수신 시에는 먼저 수신할 데이터의 길이를 Int64 값으로 받은 후, 그 길이 만큼 TIdBytes에 수신한다. 수신된 첫 자리는 "구분자"로 떼어내고 나머지 부분을 암호 해독처리 한다. 받은 데이터의 "구분자"가 오류관련 "구분자"이면 오류 메세지를 띄우고 전송한 "구분자"와 수신된 "구분자"가 다를 때도 오류 메세지를 띄운다. 정상적으로 수신이 이루어졌으면 True를 반환한다.
// 데이터 수신
function TdmSIMLaz.UF_SIMLazRcv(var ACmd: UInt8; out ACont: TIdBytes): Boolean;
var
  s: string;
  l_Cmd: UInt8;
  l_Len: Int64;
  l_Data: TIdBytes;
begin
  Result := False;
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  l_Len := IdTCPConn.IOHandler.ReadInt64(False);
  if l_Len <= 0 then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ301, mtError, [mbOK], 0);
    Exit;
  end;

  l_Data := nil;
  IdTCPConn.IOHandler.ReadBytes(l_Data, l_Len);

  l_Cmd := l_Data[0];
  SetLength(ACont, l_Len - 1);
  Move(l_Data[1], ACont[0], l_Len - 1);
  GP_SIMLazDec(ACont);

  if l_Cmd = smlc_ERROR then
  begin
    s := BytesToString(ACont, u_Encoding, u_Encoding);
    if s = '' then s := ERR_SIMLAZ999;
    ACmd := l_Cmd;
    MessageDlg(Application.Title, s, mtError, [mbOK], 0);
  end
  else if l_Cmd <> ACmd then
  begin
    ShowMessage(Chr(ACmd) + ':' + Chr(l_Cmd));
    ACmd := l_Cmd;
    MessageDlg(Application.Title, ERR_SIMLAZ002, mtError, [mbOK], 0);
  end
  else Result := True;
end;


로그인에 사용할 암호는 역함수가 필요없으므로 SHA256를 사용한다. Indy에 포함된 "TIdHashSHA256" 객체를 이용해서 SHA256 값을 구한다.
// SHA256
function TdmSIMLaz.UF_SIMLazSHA(const AValue: string): string;
var
  l_hTmp: TIdHash;
begin
  Result := '';
  if not IdSSLOpenSSLHeaders.Load then Exit;

  l_hTmp := TIdHashSHA256.Create;
  try
    Result := l_hTmp.HashStringAsHex(AValue);
  finally
    FreeAndNil(l_hTmp);
  end;
end;


사용자 목록은 현재 접속된 클라이언트의 접속 정보를 탭으로 구분된 문자열 레코드 형태로 서버에서 수신받는다.
// 사용자 목록
function TdmSIMLaz.UF_SIMLazList: string;
var
  l_Cmd: UInt8;
  l_Data: TIdBytes;
  l_Stream: TStringStream;
begin
  Result := '';
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  u_Lock.Enter;
  try
    l_Data := nil;
    l_Cmd := smlc_USERLIST;
    if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

    l_Data := nil;
    if UF_SIMLazRcv(l_Cmd, l_Data) then
    begin
      l_Stream := TStringStream.Create('');
      try
        WriteTIdBytesToStream(l_Stream, l_Data);
        l_Stream.Position := 0;
        Result := l_Stream.DataString;
      finally
        FreeAndNil(l_Stream);
      end;
    end;
  finally
    u_Lock.Leave;
  end;
end;


프로그램 ID에 해당되는 최신 버전의 정보를 서버에서 가져와서 사용 중인 파일과 다를 경우 최신 버전의 파일을 가져와 교체한다. 이때 실행 파일뿐만 아니라 각종 이미지나 설정파일, dll 등등을 zip 파일에 넣어서 처리할 수 있다. 버전 유치 저리는 하나의 실행파일에서 할 수도 있겠지만 본 예제에서는 버전 유지용 프로그램과 업무용 클라이언트를 분리해서 관리한다.
// 버전 정보
function TdmSIMLaz.UF_SIMLazVer(AID: string): string;
var
  l_Cmd: UInt8;
  l_Data: TIdBytes;
begin
  Result := '';
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  u_Lock.Enter;
  try
    l_Data := nil;
    l_Cmd := smlc_VERSION;
    AppendString(l_Data, AID, -1, u_Encoding, u_Encoding);
    if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

    l_Data := nil;
    if UF_SIMLazRcv(l_Cmd, l_Data) then
      if Length(l_Data) > 0 then Result := BytesToString(l_Data, u_Encoding, u_Encoding);
  finally
    u_Lock.Leave;
  end;
end;

// 파일 가져오기
procedure TdmSIMLaz.UP_SIMLazFile(AID, AFileName: string);
var
  l_Cmd: UInt8;
  l_Data: TIdBytes;
  l_Stream: TMemoryStream;
begin
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  u_Lock.Enter;
  l_Stream := TMemoryStream.Create;
  try
    l_Data := nil;
    l_Cmd := smlc_GETFILE;
    AppendString(l_Data, AID, -1, u_Encoding, u_Encoding);
    if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

    l_Data := nil;
    if UF_SIMLazRcv(l_Cmd, l_Data) then
    begin
      WriteTIdBytesToStream(l_Stream, l_Data);
      l_Stream.Position := 0;
      if FileExists(AFileName) then DeleteFile(AFileName);
      l_Stream.SaveToFile(AFileName);
    end;
  finally
    FreeAndNil(l_Stream);
    u_Lock.Leave;
  end;
end;


로그인시 암호는 SHA256으로 인코딩된 값이어야한다. 로그인에 성공하면 사용자 ID와 사용자명, 관리자 여부를 DataModule의 public 변수에 저장한다.
// 로그인
function TdmSIMLaz.UF_SIMLazLogin(AID, APasswd: string): Boolean;
var
  s: string;
  l_Cmd: UInt8;
  l_Data: TIdBytes;
begin
  Result := False;
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  u_Lock.Enter;
  try
    l_Data := nil;
    l_Cmd := smlc_LOGIN;
    s := AID + SIMLAZ_EOF + APasswd;
    AppendString(l_Data, s, -1, u_Encoding, u_Encoding);
    if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

    l_Data := nil;
    if UF_SIMLazRcv(l_Cmd, l_Data) then
    begin
      if Length(l_Data) <= 0 then Exit;
      u_uid := AID;
      u_admin_flag := Chr(l_Data[0]) = '1';
      u_uname := BytesToString(l_Data, 2, -1, u_Encoding, u_Encoding);
      Result := True;
    end;
  finally
    u_Lock.Leave;
  end;
end;


Query 실행 함수에서는 데이터 조작용 SQL 구문을 서버로 보내서 실행시키고 적용된 레코드 수를 반환 받는다.
// Query 실행
function TdmSIMLaz.UF_SIMLazExec(AQuery: string): Int32;
var
  l_Cmd: UInt8;
  l_Data: TIdBytes;
begin
  Result := 0;
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  u_Lock.Enter;
  try
    l_Data := nil;
    l_Cmd := smlc_EXECQUERY;
    AppendString(l_Data, AQuery, -1, u_Encoding, u_Encoding);
    if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

    l_Data := nil;
    if UF_SIMLazRcv(l_Cmd, l_Data) then Result := StrToInt(BytesToString(l_Data));
  finally
    u_Lock.Leave;
  end;
end;


데이터 조회 시 서버로부터 받은 XML을 Stream으로 복사한 후 TBufDataset에 적용시킨다. 이를 위해서 uses에 "XMLDatapacketReader"를 추가해야한다. ALoadFields가 True일 때는 참조된 TCustomBufDataset의 Field를 모두 초기화하고 XML에 지정된 Field로 재구성한다. False인 경우에는 Field 구조를 그대로 유지한 채 DataSet의 내용만 갱신한다. 일반적으로 내용이 고정된 프로그램에서는 Field 구조가 유지되어야하지만 하나의 TBufDataset을 이용해서 가변적인 데이터를 조회하는 경우에는 Field 구조를 매번 삭제하고 새로 생성해야한다.
// 데이터 가져오기
procedure TdmSIMLaz.UP_SIMLazOpen(ADataSet: TCustomBufDataset; AQuery: string; const ALoadFields: Boolean);
var
  l_Cmd: UInt8;
  l_Data: TIdBytes;
  l_Stream: TMemoryStream;
begin
  if not IdTCPConn.Connected then
  begin
    MessageDlg(Application.Title, ERR_SIMLAZ302, mtError, [mbOK], 0);
    Exit;
  end;

  u_Lock.Enter;
  try
    l_Data := nil;
    l_Cmd := smlc_OPENQUERY;
    AppendString(l_Data, AQuery, -1, u_Encoding, u_Encoding);
    if not UF_SIMLazSnd(l_Cmd, l_Data) then Exit;

    if UF_SIMLazRcv(l_Cmd, l_Data) then
    begin
      if ADataSet.Active then ADataSet.Close;
      if ALoadFields then ADataSet.Clear;
      l_Stream := TMemoryStream.Create;
      try
        WriteTIdBytesToStream(l_Stream, l_Data);
        l_Stream.Position := 0;
        ADataSet.LoadFromStream(l_Stream, dfXML);
      finally
        l_Stream.Free;
      end;
    end;
  finally
    u_Lock.Leave;
  end;
end;


+ -

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