From YYpBD's MediaWiki
[참고]TServerWinSocket의 TServerClinetThread관리
TServerWinSocket의 TServerClinetThread관리
BY YSU
먼저 non-blocking mode에 대해서는 해당 사항 없음을 밝히다.
TServerWinSocket에서 threadblocking mode인 경우 service를 TServerClinetThread
혹은 그 후손이 담당하게 된다. TServerWinSocket에서 TServerClinetThread를 사용하
는 관리 방안을 분석해 본다.
1. TServerClientThread를 얻는다.
TServerClientThread을 TServerWinSocket이 얻는 것은 Accept후
TServerClinetSocket을 만들고 GetServerThread에서 얻는다. 다음은 Source다
01:function TServerWinSocket.GetServerThread(...);
02: var
03: I: Integer;
04: begin
05: Result := nil;
06: FListLock.Enter;
07: try
08: for I := 0 to FActiveThreads.Count - 1 do
09: if TServerClientThread(FActiveThreads[I]).ClientSocket = nil then
10: begin
11: Result := FActiveThreads[I];
12: Result.ReActivate(ClientSocket);
13: Break;
14: end;
15: finally
16: FListLock.Leave;
17: end;
18: if Result = nil then
19: begin
20: if Assigned(FOnGetThread) then FOnGetThread(Self,ClientSocket,Result);
21: if Result = nil then Result := DoCreateThread(ClientSocket);
22: end;
23: end;
line 08 ~ 14의 for문을 보면 TServerClientThread(FActiveThreads[I]).ClientSocket
가 nil인것을 IDLE상태로 본다. 이러한 Thread는 재활용 된다. 여기서 추측하는데로
Service가 끝나면 ClinetSocket을 nil로 setting하여 노는 상태를 표시한다.
생성하지 않고 재활용인 경우 ReActivate로 TServerWinSocket에 등록하고 event
handler를 연결하고, signaled 상태로 바꾸어 ClinetExcute를 시작하는등의 필요한
조치를 취한다. 이 부분은 뒤에 다시 다룬다.
line 20은 사용자가 지정한 event handler를 실행하여 Result에 Thread를 얻는다.
Line 21은 사용자 event handler를 지정하지 않은 경우 DoCreateThread로 생성한다
function TServerWinSocket.DoCreateThread(...);
begin
Result := TServerClientThread.Create(False, ClientSocket);
end;
로 TServerClinetThread를 생성한다.
2. TServerClientThread의 생성
위에서 재활용인 경우를 제외하고, 새로 생성하는 경우를 보자. 다음은 Source다
01:constructor TServerClientThread.Create(CreateSuspended: Boolean;
02: ASocket: TServerClientWinSocket);
03:begin
04: FreeOnTerminate := True;
05: FEvent := TSimpleEvent.Create;
06: inherited Create(True);
07: Priority := tpHigher;
08: ReActivate(ASocket);
09: if not CreateSuspended then Resume;
10:end;
line 8을 보면 ReActivate가 있다. 이건 위에서 재활용인 경우에 사용한 건데 여기도
있다. 이게 thread의 시작과 관계가 있음을 알수 있다.
line 5를 보면 TSimpleEvent가 있는데 이건 source/internet/syncobjs.pas를 보면
있다. 함 참고하자. 이것이 thread를 시작 시키는 열쇠라는걸 염두에 두고 보자.
3. ReActivate의 동작
ReActivate가 thread의 시작과 초기화등에 관여 한다는건 쉽게 예상할수 있다.
다음 source를 보자.
01:procedure TServerClientThread.ReActivate(ASocket: TServerClientWinSocket);
02:begin
03: FClientSocket := ASocket;
04: if Assigned(FClientSocket) then
05: begin
06: FServerSocket := FClientSocket.ServerWinSocket;
07: FServerSocket.AddThread(Self);
08: FClientSocket.OnSocketEvent := HandleEvent;
09: FClientSocket.OnErrorEvent := HandleError;
10: FEvent.SetEvent;
11: end;
12:end;
line 7은 TServerWinSocket에 등록하는 함수다. 만일 기등록 된것은 무시된다.
line 8,9는 TServerSocket Component의 사용자 event handler를
TServerClinetWinSocket으로 연결하는 부분이다. 이건 재활용이라도 Socket이 새로
생성 된거이니 다시 연결해 주어야 한다.
line 10은 다음에 Exute에서 설명한다.
4. Excute에서의 동작
재활용하는 이유는 TServerClinetThread를 매번 생성하지 않고 기존의 Thread를
이용하면 보다 빠른 service를 할 수 있기 때문이다. 특이한 건 KeepInCache에 10개
를 지정하고 11개째 접속을 하면 접속이 된다. KeepInCache의 뜻은 접속 갯수를
지정하는게 아니라 service하는 TServerClinetThread의 재활용을 몇개나 보유하고
있느냐다. 서설은 치우고 먼저 Source를 보자.
01:procedure TServerClientThread.Execute;
02:begin
03: FServerSocket.ThreadStart(Self);
04: try
05: try
06: while True do
07: begin
08: if StartConnect then ClientExecute;
09: if EndConnect then Break;
10: end;
11: except
12: HandleException;
13: KeepInCache := False;
14: end;
15: finally
16: FServerSocket.ThreadEnd(Self);
17: end;
18:end;
주변을 제외하면 line 6 ~ 9가 핵심 부분이다. 이걸 보면 무한 loop안에서
startconnect함수의 결과가 참이면, clinetexcute를 실행하고, 다시 endconnect
함수의 결과가 참이면 loop를 빠저나가서 thread가 끝나게 된다. 아울러 thread
자체도 파괴 된다. 실제로 service를 하는 부분은 clinetexcute임을 알수 있다
그럼 startconnect와 endconnect를 살펴보자
function TServerClientThread.EndConnect: Boolean;
begin
FClientSocket.Free;
FClientSocket := nil;
Result := Terminated or not KeepInCache;
end;
비교적 간단한 함수고 주의해서 볼 점은 FClinetSocket := nil;이다 여기서 nil로
설정하고 위에서 본 GetServerThread에서 nil인걸 노는 thread로 보고 재활용한다.
그리고 KeepInCache가 참이아니면 종료한다. KeepInCache는 TServerWinSocket.Add
에서 list에 추가할때 true로 지정 된다.
function TServerClientThread.StartConnect: Boolean;
begin
if FEvent.WaitFor(INFINITE) = wrSignaled then
FEvent.ResetEvent;
Result := not Terminated;
end;
여기서 FEvent가 다시 나왔다. 그리고 WaitFor라는 메소드를 호출한다. 그리고
ResetEvent를 호출하는데 위에서 미뤼놓은 FEvent.SetEvent와 함께 살펴보자.
먼저 /source/internet/syncobjs.pas안에 내용을 살펴보자.
procedure TEvent.SetEvent;
begin
Windows.SetEvent(Handle);
end;
procedure TEvent.ResetEvent;
begin
Windows.ResetEvent(Handle);
end;
function TEvent.WaitFor(Timeout: Longint): TWaitResult;
begin
case WaitForSingleObject(Handle, Timeout) of
WAIT_ABANDONED: Result := wrAbandoned;
WAIT_OBJECT_0: Result := wrSignaled;
WAIT_TIMEOUT: Result := wrTimeout;
WAIT_FAILED:
begin
Result := wrError;
FLastError := GetLastError;
end;
else
Result := wrError;
end;
end;
핵심은 Windows.SetEvent(Handle)와 Windows.ResetEvent(Handle) 그리고
WaitForSingleObject(Handle, Timeout)이다. 그럼 먼저 WaitForSingleObject를
source/rtl/win/windows.pas에서 찾아보자
function WaitForSingleObject; external kernel32 name 'WaitForSingleObject';로
이건 API함수다 기능은 MSDN에보면 이렇게 써있다.
The WaitForSingleObject function returns when one of the following occurs:
* The specified object is in the signaled state.
* The time-out interval elapses.
즉 handle이 signaled 상태일때 까지 또는 Timeout일때 까지 기다린다. 이건
thread를 정지시킨 후 재 시작까지 기다리는 기능을 한다. 이동안 thread는 아주
적은 CPU를 사용할 뿐이다. 그럼 SetEvent(Handle)와 ResetEvent(Handle)은 보나
마나 signal 상태를 풀어주거나 잠그는 기능을 할것이다. 그럼 확인해 보자
function ResetEvent; external kernel32 name 'ResetEvent'
* The SetEvent function sets the state of the specified event object
to signaled.
function SetEvent; external kernel32 name 'SetEvent' 역시 API고
* The ResetEvent function sets the state of the specified event object
to nonsignaled.
SetEvent는 signal 상태로 하고 ResetEvent는 non-signal상태로 만드는 함수다.
자 그럼 정리를 해보자 ReActivate는 thread의 시작시 호출 되는데 거기서 SetEvent
로 signal 상태로 만들고 Excute의 startconnect에서 signal상태를 기다린후
clinetexcute를 실행하고 거기서 service를 처리한 후 clinetexcute가 종료하면
endconnect에서 다시 non-signal 상태로 만든후 KeepInCache가 지정 않된경우 종료
한다. 만일 KeepInCache가 지정 되 있으면 종료는 않하고 다시 while true loop를
타고 돌아 startconnect에서 ReActivate에서 signal 상태로 만들때까지 기다리게
된다.
5. Service개발은 TServerClientThead의 ClinetExcute를 override해서 구현하고,
TServerSocket의 OnGetThread event handler에서 생성하여 돌려준다.