내용은 이렇습니다. TCP 서버인데 클라이언트로부터 LogIn 데이터가 오면 ListView에 어디서 온 것인지 기록합니다. 그 후 작업에 필요한 데이터가 오면 그 데이터를 메모에 복사합니다. 그럼 타이머가 항상 돌고 있다가 메모가 공백이 아니면 mysql에서 데이터를 추출하여 오라클에 Insert or Update or Delete 작업을 합니다. 타이머의 Interval은 250 이고 작업을 하기전에 타이머를 Enabeld := False를 해 두고 작업을 끝나면 다시 Enabled := True를 해 둡니다.
그런데 문제는 비교적 이 서버가 잘 죽는다는데 있습니다. 계속 몇일을 살아 있어야 하는데, 흑흑...
제 소스에 문제가 많은 건가요? 고수님들의 조언을 구합니다.
<!--CodeS-->
vunit pMain;
interface
uses
Windows....;
type
TCommBlock = packed record
Command : String[10];
UserID : String[20];
ComName : String[20];
SafemanID : String[12];
IDNo : String[13];
end;
TfrmMain = class(TForm)
suiForm: TsuiForm;
TrayIcon1: TTrayIcon;
PopupMenu1: TPopupMenu;
N1: TMenuItem;
N2: TMenuItem;
Timer_ConnectCheck: TTimer;
IdTCPServer: TIdTCPServer;
TimerAdd: TTimer;
Panel2: TPanel;
MemoAdd: TsuiMemo;
MemoDel: TsuiMemo;
ListBox: TsuiListBox;
Label2: TLabel;
Panel1: TPanel;
ListView: TsuiListView;
Label3: TLabel;
Panel3: TPanel;
Label4: TLabel;
Label5: TLabel;
eIP: TsuiEdit;
ePort: TsuiEdit;
Panel4: TPanel;
btnConfig: TsuiButton;
btnStartServer: TsuiButton;
btnStopServer: TsuiButton;
TimerStart: TTimer;
TimerDel: TTimer;
procedure TrayIcon1DblClick(Sender: TObject);
procedure N1Click(Sender: TObject);
procedure N2Click(Sender: TObject);
procedure Timer_ConnectCheckTimer(Sender: TObject);
procedure IdTCPServerExecute(AThread: TIdPeerThread);
procedure btnStopServerClick(Sender: TObject);
procedure btnStartServerClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure IdTCPServerConnect(AThread: TIdPeerThread);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure IdTCPServerDisconnect(AThread: TIdPeerThread);
procedure IdTCPServerException(AThread: TIdPeerThread;
AException: Exception);
procedure IdTCPServerListenException(AThread: TIdListenerThread;
AException: Exception);
procedure btnConfigClick(Sender: TObject);
procedure TimerAddTimer(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure TimerStartTimer(Sender: TObject);
procedure TimerDelTimer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure ActiveChk;
function CurrConnect: Integer;
function ConnectChk: Boolean;
function Oracle_Start: Boolean;
function Oracle_Stop: Boolean;
procedure MsgAdd(S: String);
procedure ReadIni;
procedure FailWrite(S: String);
end;
var
frmMain: TfrmMain;
pvIniFile: TiniFile;
pvSList: TStringList;
pvType: Integer;
pvOraName, pvOraPass, pvOraTNS: String;
pvFormCloseFlag, pvAddFlag, pvDelFlag,
pvNoAdd, pvNoEdit, pvNoDelete, pvAutoStart, pvConnect: Boolean;
implementation
uses pDM, pConfig;
{$R *.DFM}
function StrCut(S: String; Count: Integer): String;
begin
if ByteType(S, Count) = mbLeadByte Then
Result := Copy(S, 1, Count-1)
else
Result := Copy(S, 1, Count);
end;
procedure TfrmMain.FailWrite(S: String);
var
fp1: TextFile;
f_e: String;
begin
if not DirectoryExists(ExtractFilePath(ParamStr(0)) + 'Log') then ForceDirectories(ExtractFilePath(ParamStr(0)) + 'Log');
f_e := ExtractFilePath(ParamStr(0)) + 'Log\' + FormatDateTime('YYYYMMDD',Now) + '.txt'; '
AssignFile(fp1,f_e);
if FileExists(f_e) then Append(fp1) else ReWrite(fp1);
Writeln(fp1,S);
CloseFile(fp1);
end;
procedure TfrmMain.MsgAdd(S: String);
var
vMsg: String;
begin
vMsg := '[ '+FormatDateTime('YYYY-MM-DD HH:NN:SS',Now)+' ] ' + S;
ListBox.Items.Insert(0, vMsg);
FailWrite(vMsg);
if ListBox.Items.Count > 1000 then ListBox.Items.Clear;
end;
function TfrmMain.CurrConnect: Integer;
var
List : TList;
begin
List := IdTCPServer.Threads.LockList;
try
Result := List.Count;
finally
IdTCPServer.Threads.UnlockList;
end;
end;
procedure TfrmMain.TrayIcon1DblClick(Sender: TObject);
begin
Show;
end;
procedure TfrmMain.N1Click(Sender: TObject);
begin
Show;
end;
procedure TfrmMain.N2Click(Sender: TObject);
begin
pvFormCloseFlag := False;
Close;
end;
procedure TfrmMain.Timer_ConnectCheckTimer(Sender: TObject);
var
Loop, aLoop: Integer;
IP, Handle: String;
List : TList;
Find: Boolean;
begin
if ListView.Items.Count = 0 then Exit;
if not IdTCPServer.Active then Exit;
List := IdTCPServer.Threads.LockList;
try
for Loop := ListView.Items.Count - 1 downto 0 do
begin
Find := False;
IP := ListView.Items.Item[Loop].SubItems[1];
Handle := ListView.Items.Item[Loop].SubItems[3];
for aLoop := 0 to List.Count -1 do
begin
if (IP = TIdPeerThread(List.Items[aLoop]).Connection.Socket.Binding.PeerIP) and
(Handle = IntToStr(TIdPeerThread(List.Items[aLoop]).Connection.Socket.Binding.Handle)) then
begin
Find := True;
Break;
end;
end;
if not Find then ListView.Items.Item[Loop].Delete;
end;
finally
IdTCPServer.Threads.UnlockList;
end;
end;
procedure TfrmMain.IdTCPServerExecute(AThread: TIdPeerThread);
var
// Command : String;
FindItem, AddItem: TListItem;
var
CommBlock: TCommBlock;
RetrunStr: String;
begin
if not AThread.Terminated and AThread.Connection.Connected then
begin
AThread.Connection.ReadBuffer(CommBlock, SizeOf (CommBlock));
if (CommBlock.Command = 'Add') then
begin
MemoAdd.Lines.Add(CommBlock.SafemanID+'|'+CommBlock.IDNo);
end else
if (CommBlock.Command = 'Edit') then
begin
MemoAdd.Lines.Add(CommBlock.SafemanID+'|'+CommBlock.IDNo);
end else
if (CommBlock.Command = 'Del') then
begin
MemoDel.Lines.Add(CommBlock.SafemanID+'|'+CommBlock.IDNo);
end else
if (CommBlock.Command = 'LogOut') then
begin
FindItem := ListView.FindCaption(-1, CommBlock.UserID, False, False, False);
if (FindItem <> nil) and (FindItem.SubItems[0] = CommBlock.ComName) then
begin
MsgAdd('LogOut: '+ CommBlock.UserID +'['+AThread.Connection.Socket.Binding.PeerIP+']');
FindItem.Delete;
end;
end else
if (CommBlock.Command = 'LogIn') then
begin
FindItem := ListView.FindCaption(-1, CommBlock.UserID, False, False, False);
if FindItem = nil then
begin
MsgAdd('LogIn: '+ CommBlock.UserID +'['+AThread.Connection.Socket.Binding.PeerIP+']');
RetrunStr := 'LogIn';
ListView.Items.BeginUpdate;
AddItem := ListView.Items.Add;
AddItem.Caption := CommBlock.UserID; // ID
AddITem.SubItems.Add(CommBlock.ComName); // Name
AddITem.SubItems.Add(AThread.Connection.Socket.Binding.PeerIP); // IP
AddITem.SubItems.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss',Now)); // Time
AddITem.SubItems.Add(IntToStr(AThread.Connection.Socket.Binding.Handle));
ListView.Items.EndUpdate;
end else
begin
if (FindItem.Caption = CommBlock.UserID) and
(FindItem.SubItems[0] = CommBlock.ComName) and
(FindItem.SubItems[1] = AThread.Connection.Socket.Binding.PeerIP) then RetrunStr := 'LogIn' else
begin
MsgAdd('Using: '+ CommBlock.UserID +'['+AThread.Connection.Socket.Binding.PeerIP+']');
RetrunStr := 'Using';
end;
end;
CommBlock.Command := RetrunStr;
CommBlock.UserID := '';
CommBlock.SafemanID := '';
CommBlock.IDNo := '';
AThread.Connection.WriteBuffer(CommBlock, SizeOf(CommBlock));
end;
end;
end;
procedure TfrmMain.ActiveChk;
begin
if IdTCPServer.Active then
begin
suiForm.Caption := 'ResNet119 - Server [서버 시작]';
btnStartServer.Enabled := False;
btnStopServer.Enabled := True;
MsgAdd('서버가 시작되었습니다.');
end else
begin
suiForm.Caption := 'ResNet119 - Server [서버 중지]';
btnStartServer.Enabled := True;
btnStopServer.Enabled := False;
MsgAdd('서버가 중지되었습니다.');
end;
end;
procedure TfrmMain.btnStopServerClick(Sender: TObject);
begin
if CurrConnect > 0 then
begin
Application.MessageBox(PChar('접속중인 사용자가 있어 종료할 수 없습니다.'),'알림',MB_OK or MB_ICONINFORMATION);
Exit;
end;
try
IdTCPServer.Active := False;
// IdTCPServer.Bindings.Clear;
ActiveChk;
except
on E : Exception do Application.MessageBox(PChar('서버 중지를 실패하였습니다.'+#13#10+#13#10+'오류 내용: '+E.Message),'알림',MB_OK or MB_ICONINFORMATION);
end;
end;
procedure TfrmMain.btnStartServerClick(Sender: TObject);
{var
Binding : TIdSocketHandle;}
begin
try
if IdTCPServer.Active then Exit;
IdTCPServer.DefaultPort := StrToInt(ePort.Text);
IdTCPServer.Active := True;
ActiveChk;
except
on E : Exception do Application.MessageBox(PChar('서버 시작을 실패하였습니다.'+#13#10+#13#10+'오류 내용: '+E.Message),'알림',MB_OK or MB_ICONINFORMATION);
end;
end;
procedure TfrmMain.ReadIni;
var
IniFile : TIniFile;
begin
IniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'SrvConfig.ini');
try
pvOraName := frmDM.DecryptData( IniFile.ReadString('Oracle', 'UserName', '') );
pvOraPass := frmDM.DecryptData( IniFile.ReadString('Oracle', 'Passwd', '') );
pvOraTNS := frmDM.DecryptData( IniFile.ReadString('Oracle', 'TNS', '') );
pvAutoStart := IniFile.ReadBool ('Etc', 'AutoStart', False);
pvType := IniFile.ReadInteger('Save', 'Type', -1);
pvNoAdd := IniFile.ReadBool ('Data', 'NoAdd', False);
pvNoEdit := IniFile.ReadBool ('Data', 'NoEdit', False);
pvNoDelete := IniFile.ReadBool ('Data', 'NoDelete', False);
finally
IniFile.Free;
end;
end;
function DoInstanceExist(WndTitle, NicName: string): Boolean;
var
hSem: THandle;
hWndMe: HWnd;
semNm, wTtl: array[0..256] of Char;
begin
Result := False;
// Pascal strings 을 Char형 배열로 복사;
StrPCopy(semNm, NicName);
StrPCopy(wTtl, WndTitle);
// 메모리에 Semaphore 생성
hSem := CreateSemaphore(nil, 0, 1, semNm);
// semaphore가 이미 존재하는지 검사
if ((hSem <> 0) and (GetLastError() = ERROR_ALREADY_EXISTS)) then
begin
CloseHandle(hSem);
// 이전 instance 검색
hWndMe := FindWindow(nil, wTtl);
if (hWndMe <> 0) then
begin
if IsIconic(hWndMe) then
ShowWindow(hWndMe, SW_SHOWNORMAL)
else
SetForegroundWindow(hWndMe);
end;
Result := True;
end;
end;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
if DoInstanceExist(Self.Caption, 'ResNet119_Server.exe') then
begin
MessageDlg(#13#10 + 'ResNet119 - Server 프로그램이 이미 실행중입니다.', mtWarning, [mbOk], 0);
Halt(0); // 중복실행 방지를 위해...
end;
pvAddFlag := False;
pvDelFlag := False;
pvFormCloseFlag := False;
end;
procedure TfrmMain.IdTCPServerConnect(AThread: TIdPeerThread);
begin
MsgAdd('Connect: '+AThread.Connection.Socket.Binding.PeerIP);
end;
procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if pvFormCloseFlag then
begin
CanClose := False;
Hide;
end else
begin
if CurrConnect > 0 then
begin
CanClose := False; pvFormCloseFlag := True;
Application.MessageBox(PChar('접속중인 사용자가 있어 종료할 수 없습니다.'),'알림',MB_OK or MB_ICONINFORMATION);
end else
begin
if Application.MessageBox(PChar('프로그램을 종료하시겠습니까?'), PChar('알림'), MB_YESNO + MB_ICONQUESTION) <> IDYes then
begin
pvFormCloseFlag := True;
CanClose := False
end else
begin
pvSList.Free;
btnStopServerClick(Sender);
end;
end;
end;
end;
procedure TfrmMain.IdTCPServerDisconnect(AThread: TIdPeerThread);
begin
MsgAdd('Disconnect: '+AThread.Connection.Socket.Binding.PeerIP);
end;
procedure TfrmMain.btnConfigClick(Sender: TObject);
begin
frmConfig := TfrmConfig.Create(Self);
try
frmConfig.ShowModal;
finally
frmConfig.Free;
end;
ReadIni;
end;
function TfrmMain.ConnectChk: Boolean;
begin
if frmDM.Ora_Database.Connected then
begin
Result := True;
MsgAdd('오라클에 접속되었습니다.');
end else
begin
Result := False;
MsgAdd('오라클 접속이 해제되었습니다.');
end;
end;
function TfrmMain.Oracle_Start: Boolean;
begin
Result := False;
if frmDM.Ora_Database.Connected then
begin
Result := True;
Exit;
end;
if (Trim(pvOraName) <> '') and (Trim(pvOraPass) <> '') and (Trim(pvOraTNS) <> '') then
begin
frmDM.Ora_Database.Login := pvOraName;
frmDM.Ora_Database.Password := pvOraPass;
frmDM.Ora_Database.Database := pvOraTNS;
try
frmDM.Ora_Database.Connect;
Result := ConnectChk;
except
on E: Exception do MsgAdd('오라클 접속 시도중에 오류가 발생하였습니다. [오류 내용: '+E.Message+']');
end;
end else MsgAdd('오라클 접속 정보가 없이 접속을 취소하였습니다.');
end;
function TfrmMain.Oracle_Stop: Boolean;
begin
Result := True;
if pvAddFlag or pvDelFlag then
begin
MsgAdd('작업중이므로 오라클 접속을 해제할 수 없습니다.');
Exit;
end;
try
frmDM.Ora_Database.Disconnect;
Result := ConnectChk;
except
on E : Exception do
begin
Result := True;
MsgAdd('오라클 접속 해제를 실패하였습니다. [Err Msg: '+E.Message+']');
end;
end;
end;
procedure TfrmMain.TimerAddTimer(Sender: TObject);
var
vChk: Boolean;
vFindKey: String;
function UpdateJob4: Boolean;
begin
// mysql에서 찾은 데이터를 오라클로 Update 한다.
end;
function InsertJob4: Boolean;
begin
// mysql에서 찾은 데이터를 오라클로 Insert 한다.
end;
begin
TimerAdd.Enabled := False;
if Trim(MemoAdd.Text) <> '' then
begin
MemoAdd.Clear;
if pvType = 4 then
begin
if FileAge(ExtractFilePath(Application.ExeName) + 'EtcCode5.ini') > 0 then
begin
frmDM.My_AddFindQuery4.Close;
frmDM.My_AddFindQuery4.Open;
MsgAdd('작업할 데이터 수: '+IntToStr(frmDM.My_AddFindQuery4.RecordCount));
if frmDM.My_AddFindQuery4.RecordCount > 0 then
begin
if Oracle_Start then
begin
pvAddFlag := True;
while not frmDM.My_AddFindQuery4.Eof do
begin
vChk := False;
vFindKey := FastReplace(frmDM.My_AddFindQuery4.FieldByName('ssn').AsString,'-','');
MsgAdd('검색 키워드: '+vFindKey);
frmDM.Ora_AddFindQuery4.Close;
frmDM.Ora_AddFindQuery4.ParamByName('id_no').AsString := vFindKey;
frmDM.Ora_AddFindQuery4.Open;
if frmDM.Ora_AddFindQuery4.RecordCount > 0 then
begin
frmDM.Ora_AddFindQuery4.Close;
if not pvNoEdit then vChk := UpdateJob4 else MsgAdd('갱신 금지가 설정되어 있습니다.');
end else
begin
frmDM.Ora_AddFindQuery4.Close;
if not pvNoAdd then vChk := InsertJob4 else MsgAdd('추가 금지가 설정되어 있습니다.');
end;
if vChk then
begin
frmDM.My_ClearQuery4.Close;
frmDM.My_ClearQuery4.ParamByName('ssn').AsString := frmDM.My_AddFindQuery4.FieldByName('ssn').AsString;
frmDM.My_ClearQuery4.ExecSql;
frmDM.My_ClearQuery4.Close;
end;
frmDM.My_AddFindQuery4.Next;
end;
pvAddFlag := False;
Oracle_Stop;
end else MsgAdd('오라클 연결에 실패하여 작업을 취소합니다.');
end;
frmDM.My_AddFindQuery4.Close;
end else MsgAdd('운영에 필요한 파일(EtcCode5.ini)이 없어 작업을 취소합니다.');
end;
end;
TimerAdd.Enabled := True;
end;
procedure TfrmMain.FormShow(Sender: TObject);
begin
if not pvFormCloseFlag then
begin
Width := 566;
pvFormCloseFlag := True;
eIP.Text := GStack.LocalAddress;
ListView.DoubleBuffered := True;
ListBox.DoubleBuffered := True;
pvSList := TStringList.Create;
ReadIni;
Case pvType of
1: pvIniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'EtcCode2.ini');
2: pvIniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'EtcCode3.ini');
3: pvIniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'EtcCode4.ini');
4: pvIniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'EtcCode5.ini');
5: pvIniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'EtcCode6.ini');
6: pvIniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'EtcCode7.ini');
end;
MsgAdd('운영타입: '+IntToStr(pvType));
TimerStart.Enabled := True;
end;
end;
procedure TfrmMain.TimerStartTimer(Sender: TObject);
begin
TimerStart.Enabled := False;
if pvAutoStart then
begin
btnStartServerClick(Sender);
Hide;
end;
end;
procedure TfrmMain.TimerDelTimer(Sender: TObject);
var
vFindKey: String;
begin
TimerDel.Enabled := False;
if Trim(MemoDel.Text) <> '' then
begin
MemoDel.Clear;
if pvType = 4 then
begin
if pvNoAdd then MsgAdd('삭제 금지가 설정되어 있습니다.') else
begin
frmDM.My_DelFindQuery4.Close;
frmDM.My_DelFindQuery4.Open;
MsgAdd('작업할 데이터 수: '+IntToStr(frmDM.My_DelFindQuery4.RecordCount));
if frmDM.My_DelFindQuery4.RecordCount > 0 then
begin
if Oracle_Start then
begin
pvDelFlag := True;
while not frmDM.My_DelFindQuery4.Eof do
begin
vFindKey := FastReplace(frmDM.My_AddFindQuery4.FieldByName('ssn').AsString,'-','');
try
frmDM.Ora_DelQuery4.Close;
frmDM.Ora_DelQuery4.ParamByName('id_no').AsString := vFindKey;
frmDM.Ora_DelQuery4.ExecSql;
frmDM.Ora_DelQuery4.Close;
frmDM.My_DelQuery4.Close;
frmDM.My_DelQuery4.ParamByName('ssn').AsString := frmDM.My_AddFindQuery4.FieldByName('ssn').AsString;
frmDM.My_DelQuery4.ExecSql;
frmDM.My_DelQuery4.Close;
MsgAdd('데이터를 삭제하였습니다. - ['+frmDM.My_DelFindQuery4.FieldByName('safeman_id').AsString+'] '+frmDM.My_DelFindQuery4.FieldByName('name').AsString);
except
on E: Exception do MsgAdd('데이터 삭제를 실패하였습니다. - ['+frmDM.My_DelFindQuery4.FieldByName('safeman_id').AsString+'] '+frmDM.My_DelFindQuery4.FieldByName('name').AsString+' - [오류 내용: '+E.Message+']');
end;
frmDM.My_DelFindQuery4.Next;
end;
pvDelFlag := False;
Oracle_Stop;
end else MsgAdd('오라클 연결에 실패하여 작업을 취소합니다.');
end;
frmDM.My_DelFindQuery4.Close;
end;
end;
end;
TimerDel.Enabled := True;
end;
end.
<!--CodeE-->
이 이벤트는 쓰레드 안에 있기 때문에, 쓰레드 세이프 한 리스트를 사용하셔야 합니다.
아니면, 커맨드 형식을 저장해 놓으시고 메세지로 보내서 다른 곳에서 그 메세지를 받아서 처리하게 해주셔야 합니다.
동시에 리스트 한개에 Add나 Delete를 하려고 한다면 문제가 생길 수 있죠.
즉, 쓰레드 안에서 목록을 다루시면 안됩니다..