항상 그렇지만 필자의 글은 필자의 허락없이 다른곳으로 옮기거나 수정하거나 상업적인 사용을 금합니다. 링크는 가능 합니다. 진짜 맘대로 퍼가셨다가 법적 대응을 받으실수 있습니다.
( 이유는 2009년초에 불미스러운 일로 인해 필자의 저작권을 가지고 교습 및 상업적인 이용에 관해 항의를 했으나 오히려 제가 고소를 당하는 사건으로 인해 2009년 사건이후 부터 제가 직접 쓰는 모든 글에 위 내용을 기술합니다.
2009년 이전글이나 제가 다른곳에서 퍼오거나 링크하거나 하는 남의 저작물에 대해서는 위내용을 기술하지 않습니다. )
이번장은 굳이 사용하지 않아도 충분히 구현가능한 인디(Indy) 서버와 클라이언트를 이용하여 데이타 주고 받기를 쓰레드로 구성하는 방법입니다.
참고 : 이글은 강좌 비스므리한 글입니다. 따라하며 만들어 보시면 간단히 예제가 만들어집니다. 하지만, 처음부터 예제만을 원하신다면 다른 블로그나 웹사이트를 찾아보시는것이 빠를듯 합니다.
제 강좌 비스므리한 글에는 절대 예제화일이 들어있지 않습니다.
아래 소스의 내용은 여러 가지 내용을 보여 주는 것에 의의를 가지므로 최적화등을 하지는 않았습니다.( 실은 귀찮아서리 ㅜ.ㅜ )
작업 흐름은 간단합니다.
단순히 예제용으로 만들어 진 내용으로 클라이언트에서는 서버로 무한정 데이타를 보냅니다.
서버는 받은 데이타를 무작정 클라이언트로 보냅니다.( 보내고 받은 데이타는 화면에 출력합니다. )
클라이언트는 받은 데이타를 화면에 출력합니다.
간단한 내용이므로 어렵지 않고 쉽게 만들수 있습니다.( 요즘 백수로 생활하느라 간혹 글을 쓰다 삼천포로 빠지는 일이 발생합니다. ) 쓰레드는 서버에 2개(Write, Display), 클라이언트에 3개(Read,Write,Display)가 들어 갑니다.
우선 클라이언트 부터 살표 보도록 하겠습니다.
뭐 서버부터 만들어야 하지 않겠느냐? 라고 하신다면 뭐 그럴수도 있지만, 작업 흐름상 클라이언트에서 보낸다 -> 서버에서 받는다 -> 서버에서 보낸다 -> 클라이언트에서 받는다 이므로 클라이언트부터 보도록 하죠.
클라이언트의 쓰레드 구조를 보면 아래와 같습니다.
무척 간단하죠?
저장Data라는 넣을 Data나 읽은 데이타를 가지고 있을 TStringList형 입니다. 전역으로 가지고 있어도 되고 Main Form에있어도 됩니다.
원래는 읽기 전용과 쓰기 전용으로 나눠야 하지만 여기선는 단지 화면에 뿌려줄 내용이므로 하나만 사용하기로 하겠습니다.
또한 쓰레드 역시 아주 간단히 한넘은 열심히 보내고 한넘은 열심히 읽고 한넘은 열심히 출력만 하는 아주 이상 야릇한 구조를 가지지만 가장 기본적인 구조를 가진 형태로 구분 하겠습니다.
자 우선 폼에 Indy TIdTCPClient를 하나를 덜렁 올려 놓습니다.
Host와 Port를 정하고(Host는 127.0.0.1로 정하고) Port는 직접 정해보시기 바랍니다.
그리고 버튼 2개를 올립니다. 이 버튼들은 하나는 쓰레드가 돌면서보내고 받는 용도로 또 하나는 쓰레드를 중지하는 용도로 쓸 예정입니다.
주 목적은 쓰레드를 이용하여 데이타 전송이므로 우선 보내기용 쓰레드 하나를 정의합니다.
보내기 전용이므로 아래와 같이 TWriteThread라고 붙여진 이름의 쓰레드를 만들어 보기로 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
TWriteThread = class(TThread) private FsData: TStringList; FIdClient: TIdTCPClient; FiCnt: Integer; FtCS: TCriticalSection; procedure SetsData(const Value: TStringList); procedure SetIdClient(const Value: TIdTCPClient); procedure SetiCnt(const Value: Integer); procedure SettCS(const Value: TCriticalSection); protected procedure Execute; override; public constructor Create; destructor Destroy; override; property sData : TStringList read FsData write SetsData; property IdClient : TIdTCPClient read FIdClient write SetIdClient; property iCnt : Integer read FiCnt write SetiCnt; property tCS : TCriticalSection read FtCS write SettCS; end; |
요기서 IdClient의 용도는 Data를 전송하기 위한 Indy TidTCPClient입니다.
sData는 전역적으로 사용되어질 TStringList의 값을 받아 오는 넘입니다.
tCS는 전역적으로 sData를 사용하기 때문에 sData의 값을 변경할때 다른 쓰레드에서 값을 변경하지 못하도록 막는 역활을 합니다.
라인수만 길었지 실제 내용은 별거 없습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
{ TWriteThread } constructor TWriteThread.Create; begin FreeOnTerminate := False; iCnt := 0; inherited Create( true ); end; destructor TWriteThread.Destroy; begin inherited; end; procedure TWriteThread.Execute; var cmd : String; begin if not IdClient.Connected Then exit; while not Terminated do begin IdClient.IOHandler.CheckForDisconnect(True, True); inc( FiCnt ); IdClient.IOHandler.Write(Inttostr( iCnt )+#02#$d#$a ); tCS.Enter; try sData.Add( 'Write = ' + IntToStr( FiCnt ) ); finally tCS.Leave; end; Application.ProcessMessages; WaitForSingleObject( Handle, 10 ); end; end; procedure TWriteThread.SetiCnt(const Value: Integer); begin FiCnt := Value; end; procedure TWriteThread.SetIdClient(const Value: TIdTCPClient); begin FIdClient := Value; end; procedure TWriteThread.SetsData(const Value: TStringList); begin FsData := Value; end; procedure TWriteThread.SettCS(const Value: TCriticalSection); begin FtCS := Value; end; |
뭐 언제나 그렇지만, 쓰레드를 생성할때 Create( False )로 생성하여 쓰레드가 생성되자 마자 자동으로 뺑뺑이 돌지 못하도록 합니다.
또한, FreeOnTerminate := False;로하여 종료될때 메모리가 자동으로 해제 되지 않도록 합니다.
Execute의 내용을 보시면 iCnt의 내용을 증가하면서 계속 전송하고 전송한 값을 sData에 계속 담습니다.
뭐 그닥 이런일이 많지는 않지만… 예제이니 여기까지 내용을 살펴 보시기 바랍니다.
아래는 나머지 읽고 출력하는 부분입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
TReadThread = class(TThread) private FsData: TStringList; FIdClient : TIdTCPClient; FtCS: TCriticalSection; procedure SetsData(const Value: TStringList); procedure SetIndy(const Value: TIdTCPClient); procedure SettCS(const Value: TCriticalSection); protected procedure Execute; override; public constructor Create; destructor Destroy; override; property sData : TStringList read FsData write SetsData; property IdClient : TIdTCPClient read FIdClient write SetIndy; property tCS : TCriticalSection read FtCS write SettCS; end; TDisplayThread = class(TThread) private FsData: TStringList; FListBox: TListBox; FtCS: TCriticalSection; procedure SetListBox(const Value: TListBox); procedure SetsData(const Value: TStringList); procedure SettCS(const Value: TCriticalSection); protected procedure Display; procedure Execute; override; public constructor Create; destructor Destroy; override; property ListBox : TListBox read FListBox write SetListBox; property sData : TStringList read FsData write SetsData; property tCS : TCriticalSection read FtCS write SettCS; end; ..... { TReadThread } constructor TReadThread.Create; begin FreeOnTerminate := False; inherited Create( True ); end; destructor TReadThread.Destroy; begin inherited; end; procedure TReadThread.Execute; var t : Cardinal; procedure AddData( aStr : String ); begin tCS.Enter; try sData.Add( aStr ); finally tCS.Leave; end; end; begin while not Terminated do try t := GetTickCount; repeat IdClient.IOHandler.CheckForDisconnect(True, True); IdClient.IOHandler.CheckForDataOnSource( 100 ); if GetTickCount - t > 3000 then begin AddData( '>>>>>>>>>>>>>>>>>>> Read Time Out <<<<<<<<<<<<<<<<<<<<' ); Break; end; WaitForSingleObject( Handle, 10 ); Application.ProcessMessages; until IdClient.IOHandler.InputBuffer.Size > 0; if GetTickCount - t > 3000 then Continue; tCS.Enter; try sData.Add( 'Read = ' + IdClient.IOHandler.ReadLn ); finally tCS.Leave; end; finally WaitForSingleObject( Handle, 10 ); Application.ProcessMessages; end; end; procedure TReadThread.SetIndy(const Value: TIdTCPClient); begin FIdClient := Value; end; procedure TReadThread.SetsData(const Value: TStringList); begin FsData := Value; end; procedure TReadThread.SettCS(const Value: TCriticalSection); begin FtCS := Value; end; { TDisplayThread } constructor TDisplayThread.Create; begin inherited Create( True ); end; destructor TDisplayThread.Destroy; begin inherited; end; procedure TDisplayThread.Display; var I: Integer; begin With ListBox do begin Items.Add( 'sData Count = ' + IntToStr( sData.Count ) ); for I := 0 to sData.Count - 1 do begin Items.Add( sData[i] ); ItemIndex := Count -1; end; sData.Clear; end; end; procedure TDisplayThread.Execute; begin while not Terminated do begin tCS.Enter; try Synchronize( Display ); finally tCS.Leave; end; Application.ProcessMessages; WaitForSingleObject( Handle, 10 ); end; end; procedure TDisplayThread.SetListBox(const Value: TListBox); begin FListBox := Value; end; procedure TDisplayThread.SetsData(const Value: TStringList); begin FsData := Value; end; procedure TDisplayThread.SettCS(const Value: TCriticalSection); begin FtCS := Value; end; |
DisplayThread중에 Synchronize에 의해 호출되는 부분을 보시면, CriticalSection.Enter를 걸구 sData의 모든 내용을 출력한뒤에 모든 내용을 지웁니다. 안그러면 어디까지 출력했는지 알수 있는 방법을 강구해야 하고 메모리도 계속 증가 할것이기 때문이죠.
그리 어렵지않은 소스이니 슥 훝어보시면 내용을 금방 차악 하실수 있을겁니다.
이제 이넘을 이용하는 Form에서는 아래 예제 처럼 쓰레드를 생성하고 해제 합니다.
뭐 버튼을 동작도 보시면 금방 이해가 되실겁니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
TForm2 = class(TForm) IdTCPClient1: TIdTCPClient; ListBox1: TListBox; Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } public sData : TStringList; iCnt : Integer; tCS : TCriticalSection; Read_Thread : TReadThread; write_Thread : TWriteThread; Display_Thread : TDisplayThread; end; ... procedure TForm2.FormCreate(Sender: TObject); begin tCS := TCriticalSection.Create; Read_Thread := TReadThread.Create; write_Thread := TWriteThread.Create; Display_Thread := TDisplayThread.Create; sData := TStringList.Create; end; procedure TForm2.FormDestroy(Sender: TObject); begin if write_Thread.Suspended then write_Thread.Resume; write_Thread.Terminate; if Read_Thread.Suspended then Read_Thread.Resume; Read_Thread.Terminate; if Display_Thread.Suspended then Display_Thread.Resume; Display_Thread.Terminate; write_Thread.WaitFor; Read_Thread.WaitFor; Display_Thread.WaitFor; Read_Thread.Free; write_Thread.Free; Display_Thread.Free; tCS.Free; sData.free; end; procedure TForm2.Button2Click(Sender: TObject); begin if not Read_Thread.Suspended then Read_Thread.Suspend; if not write_Thread.Suspended then write_Thread.Suspend; if not Display_Thread.Suspended then Display_Thread.Suspend; IdTCPClient1.Disconnect; end; procedure TForm2.Button1Click(Sender: TObject); begin ListBox1.Clear; IdTCPClient1.Connect; Read_Thread.tCS := tCS; Read_Thread.IdClient := IdTCPClient1; Read_Thread.sData := sData; write_Thread.tCS := tCS; write_Thread.IdClient := IdTCPClient1; write_Thread.sData := sData; Display_Thread.ListBox := ListBox1; Display_Thread.tCS := tCS; Display_Thread.sData := sData; write_Thread.Resume; Read_Thread.Resume; Display_Thread.Resume; end; |
읽는 쓰레드중에서 중요한것 하나를 집고 넘어 가겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
while not Terminated do try t := GetTickCount; repeat IdClient.IOHandler.CheckForDisconnect(True, True); IdClient.IOHandler.CheckForDataOnSource( 100 ); if GetTickCount - t > 3000 then begin AddData( '>>>>>>>>>>>>>>>>>>> Read Time Out <<<<<<<<<<<<<<<<<<<<' ); Break; end; WaitForSingleObject( Handle, 10 ); Application.ProcessMessages; until IdClient.IOHandler.InputBuffer.Size > 0; if GetTickCount - t > 3000 then Continue; |
바로 이부분입니다.
그냥 ReadLn으로 무작정 읽거나 ReadLn( 시간 )을 주고 무작정 기둘리는 것이 아니라, ( 그럼 TCP/IP의 동작이 멀티가 아니기 때문에 멈추게 됩니다. ) CheckForDisconnect와 CheckForDataOnSource를 이용하여 데이타를 기둘리게 합니다. 그래야 프로그램이 멈추거나 기다림없이 TimeOut등을 처리 할수 있습니다.
이제 서버를 한번 보죠. Indy Server자체가 하나의 ThreadList입니다. Client가 접속할때마다 쓰레드가 생성됩니다.
그러니까 Client처럼 읽어내는 Thread는 필요없고 보내고 출력하는 Thread만 필요하게 됩니다.
또 각 쓰레드에서 읽은 내용을 모두 보내야 하므로 IndyServer에 정의된 각 클라이언트의 aConText를 저장할 부분이 필요합니다.
그래서 아래와 같이 정의를 합니다.
1 2 3 4 5 6 |
TSimple = Class Public aContext : TIdContext; bRead : Boolean; data : String; End; |
Pointer형 Record로도 처리가 가능하지만, 그냥 Class가 편해서리 위와 같이 정의 하였습니다.
TSimple은 서버에 데이타가 들어올때마 데이타를 읽고 그값을 저장합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
Save_Context : Tlist; Write_Thread : TWriteThread; Display_Thread : TDisplayThread; .... procedure TForm1.Factory_ServerExecute(AContext: TIdContext); var CMD : String; UP_Ver, UP_FileName : String; 심플 : TSimple; begin CMD := ''; If AContext.Connection.IOHandler.Connected Then CMD := AContext.Connection.IOHandler.ReadLn( #02#$d#$a, 500 ); if CMD = '' then Exit; // 아무 값도 안들어 왔으면... try 심플 := TSimple.Create; 심플.AContext := aContext; // 현재 클라이언트 접속 정보를 저장. 심플.bRead := True; 심플.data := CMD; CTS_Lock.Enter; Save_Context.add( 심플 ); // 접속 정보와 데이타를 저장... sData.Add( 'Read Data = ' + CMD ); sData.Add( 'Read Data Size: ' + inttostr(length(CMD)) ); Finally CTS_Lock.Leave; end; WaitForSingleObject( handle, 1); end; |
위에 정의한 내용은 간단하지만 핵심적인 내용이기도 합니다. 읽은 데이타를 클라이언트 정보와 함께 저장하고 그값을 처리한뒤에 다시 저장된 클라이언트 정보에 데이타를 출력하는거죠.
자 이쯤에서 서버의 Main Form을 미리보고 쓰레드를 보도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
TSimple = Class Public aContext : TIdContext; bRead : Boolean; data : String; End; ... TForm1 = class(TForm) IdAntiFreeze1: TIdAntiFreeze; Factory_Server: TIdTCPServer; BTN_ServerStart: TButton; BTN_ServerStop: TButton; ListBox1: TListBox; ... procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormShow(Sender: TObject); private public Save_Context : Tlist; Write_Thread : TWriteThread; Display_Thread : TDisplayThread; end; ... procedure TForm1.BTN_ServerStartClick(Sender: TObject); begin Factory_Server.Active := True; BTN_ServerStart.Enabled := false; Write_Thread.Resume; Display_Thread.Resume; end; procedure TForm1.BTN_ServerStopClick(Sender: TObject); var i: Integer; 리스트 : TList; begin if not write_Thread.Suspended then write_Thread.Suspend; if not Display_Thread.Suspended then Display_Thread.Suspend; Factory_Server.Active := False; 리스트 := Factory_Server.Contexts.LockList; try Try for i := 0 to 리스트.Count - 1 do begin TIdContext( 리스트[i] ).Connection.Disconnect; end; except End; finally Factory_Server.Contexts.UnlockList; end; BTN_ServerStart.Enabled := True; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); var i: Integer; begin BTN_ServerStop.Click; if write_Thread.Suspended then write_Thread.Resume; write_Thread.Terminate; if Display_Thread.Suspended then Display_Thread.Resume; Display_Thread.Terminate; write_Thread.WaitFor; write_Thread.Free; Display_Thread.WaitFor; Display_Thread.Free; Factory_Server.Active := False; CTS_Lock.Free; for i := 0 to Save_Context.Count - 1 do TSimple( Save_Context[i] ).Free; Save_Context.Free; sData.Free; end; procedure TForm1.FormCreate(Sender: TObject); begin CTS_Lock := TCriticalSection.Create; sData := TStringList.Create; Save_Context := TList.Create; Write_Thread := TWriteThread.Create; Write_Thread.tCS := CTS_Lock; Write_Thread.sData := sData; Write_Thread.Save_Context := Save_Context; Write_Thread.ListBox := ListBox1; Display_Thread := TDisplayThread.Create; Display_Thread.tCS := CTS_Lock; Display_Thread.sData := sData; Display_Thread.ListBox := ListBox1; end; procedure TForm1.Factory_ServerExecute(AContext: TIdContext); var CMD : String; UP_Ver, UP_FileName : String; 심플 : TSimple; begin CMD := ''; If AContext.Connection.IOHandler.Connected Then CMD := AContext.Connection.IOHandler.ReadLn( #02#$d#$a, 500 ); if CMD = '' then Exit; try 심플 := TSimple.Create; 심플.AContext := aContext; 심플.bRead := True; 심플.data := CMD; CTS_Lock.Enter; Save_Context.add( 심플 ); sData.Add( 'Read Data = ' + CMD ); sData.Add( 'Read Data Size: ' + inttostr(length(CMD)) ); Finally CTS_Lock.Leave; end; WaitForSingleObject( handle, 1); end; |
요기서 중요한것은 Server를 잠시 멈출때나 종료할때 입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
리스트 := Factory_Server.Contexts.LockList; try Try for i := 0 to 리스트.Count - 1 do begin TIdContext( 리스트[i] ).Connection.Disconnect; end; except End; finally Factory_Server.Contexts.UnlockList; end; |
위 내용처럼 반드시 서버에 접속된 Client들을 모두 Disconnect해줘야 난중에 예외적인 에러들을 피할수 있습니다.
LockList는 Indy서버의 ThreadList를 Locking하고 작업을 처리할수 있도록 해줍니다.
또 중요한것이 종료할때 Tlist에 들어 있는 미처 처리하지 못한 Class들을 어찌 할것인가 입니다.
모두 보내고 종료할것인가? 아님 그냥 free로 날릴것인가는 각자가 정하는 몫이죠. 필자는 여기서는 그냥 지우기로 했습니다.
아래는 서버중 Thread의 소스입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
TSimple = Class Public aContext : TIdContext; bRead : Boolean; data : String; End; TWriteThread = class(TThread) private FListBox: TListBox; FsData: TStringList; FIdClient : TIdTCPClient; FtCS: TCriticalSection; FSave_Context : Tlist; procedure SetListBox(const Value: TListBox); procedure SetsData(const Value: TStringList); procedure SetIndy(const Value: TIdTCPClient); procedure SettCS(const Value: TCriticalSection); procedure SetSave_Context(const Value: Tlist); protected procedure Execute; override; public constructor Create; destructor Destroy; override; property ListBox : TListBox read FListBox write SetListBox; property sData : TStringList read FsData write SetsData; property tCS : TCriticalSection read FtCS write SettCS; property Save_Context : TList Read FSave_Context Write SetSave_Context; end; TDisplayThread = class(TThread) private FsData: TStringList; FListBox: TListBox; FtCS: TCriticalSection; procedure SetListBox(const Value: TListBox); procedure SetsData(const Value: TStringList); procedure SettCS(const Value: TCriticalSection); protected procedure Display; procedure Execute; override; public constructor Create; destructor Destroy; override; property ListBox : TListBox read FListBox write SetListBox; property sData : TStringList read FsData write SetsData; property tCS : TCriticalSection read FtCS write SettCS; end; implementation { TWriteThread } constructor TWriteThread.Create; begin FreeOnTerminate := False; inherited Create( True ); end; destructor TWriteThread.Destroy; begin inherited; end; procedure TWriteThread.Execute; var t : Cardinal; Simple : TSimple; begin while not Terminated do Begin Try if Save_Context.Count > 0 then try tCS.Enter; Simple := TSimple( Save_Context[0] ); // 저장된 클라이언트의 접속정보. if ( Simple.aContext <> nil ) And ( Simple.aContext.Connection <> nil ) And ( Simple.aContext.Connection.IOHandler <> nil ) And ( Simple.aContext.Connection.IOHandler.connected ) then begin Simple.aContext.Connection.IOHandler.Write( Simple.data + #02#$d#$a ); sData.Add('Write = ' + Simple.data ); Simple.aContext := nil; Simple.data := ''; Simple.Free; end; Finally Save_Context.Delete( 0 ); // 모든 처리가 끝났으니 바2바2 tCS.Leave; End; finally WaitForSingleObject( Handle, 10 ); Application.ProcessMessages; end; End; end; procedure TWriteThread.SetIndy(const Value: TIdTCPClient); begin FIdClient := Value; end; procedure TWriteThread.SetListBox(const Value: TListBox); begin FListBox := Value; end; procedure TWriteThread.SetsData(const Value: TStringList); begin FsData := Value; end; procedure TWriteThread.SettCS(const Value: TCriticalSection); begin FtCS := Value; end; procedure TWriteThread.SetSave_Context(const Value: Tlist); begin FSave_Context := Value; end; { TDisplayThread } constructor TDisplayThread.Create; begin inherited Create( True ); end; destructor TDisplayThread.Destroy; begin inherited; end; procedure TDisplayThread.Display; var I: Integer; begin if sData.Count = 0 then exit; With ListBox do begin Items.Add( 'sData Count = ' + IntToStr( sData.Count ) ); for I := 0 to sData.Count - 1 do begin Items.Add( sData[i] ); ItemIndex := Count -1; end; sData.Clear; end; end; procedure TDisplayThread.Execute; begin while not Terminated do begin tCS.Enter; try Synchronize( Display ); finally tCS.Leave; end; Application.ProcessMessages; WaitForSingleObject( Handle, 10 ); end; end; procedure TDisplayThread.SetListBox(const Value: TListBox); begin FListBox := Value; end; procedure TDisplayThread.SetsData(const Value: TStringList); begin FsData := Value; end; procedure TDisplayThread.SettCS(const Value: TCriticalSection); begin FtCS := Value; end; end. |
서버의 소스는 많이 간결합니다. 전체적인 흐름만 파악하면 간단하게 누구나 서버와 클라이언트를 만들수 있습니다.
참고 : 서버에 연결됬던 클라이언트가 갑자기 끊어지면 알수가 없지요? 그래서 아래 처럼 좀 무식하게 연결된 클라이언트가 있는지 체크합니다. 없으면 지워줘야 하거나 끊어줘야 합니다.
1 2 3 4 |
if ( Simple.aContext <> nil ) And ( Simple.aContext.Connection <> nil ) And ( Simple.aContext.Connection.IOHandler <> nil ) And ( Simple.aContext.Connection.IOHandler.connected ) then |
그래야 다음에 다시 참고 하는 일이 없어집니다. 여러분이 한번 만들어 보심이 좋을듯 합니다.
뭐 방법은 보낼때 검사하거나 매일정 시간마다 검사하거나 하는 방법이 있습니다.
하여간 어렵지 않은 내용을 어렵게 길게 설명하다보니 시간이 너무 흘러 버렸네요.
이번글은 여기서 마치겠습니다.
참고만 하시고 좋은 하루 되십시요.
최신 댓글