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;