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