From YYpBD's MediaWiki
[참고] TServerSocket Control의 실행 순서
TServerSocket Control의 실행 순서
By YSU
먼저 TServerSocket의 threadblocking mode를 이해하기 위해 실행순서를 분석하
므로 non-blocking부분은 제외함을 말힌다.
TServerSocket의 실행 순서를 알기 전에 간단히 server의 동작 방식을 이해해야
한다. server는 무엇인가를 service하기 위해 있는거다. service를 효율적으로 하는
방식은 꼭 computer안에만 있는건 아니다.
114안내를 보자. 전화를 걸면 맨 먼저 듣는 소리는 "안내 1234호 입니다" 다.
즉 많은 안내원중에 1234번 안내원에게 연결해 준다는 소릴게다. 그리고 안내원의
"네네 안녕하십니까?"가 나오고, 내가 번호를 물으면 안내를 해주고 전화를 ㄲㅡㄷ는다.
이게 server하고 똑같다. 그럼 server는 어떻게 동작하나, 먼저 client의 연결이
오면 연결을 받고(accept) 그리고 대기하고 있는 service에게 연결을 전달해 준다.
그리고 또 기다리다 연결을 받고(accept)또 service에게 전해주고 이걸 반복한다.
114의 "안내 1234호 입니다" 처럼. 그리고 나면 service가 그 연결을(socket) 받아
요청을 듣고, 응답을 하고, 이를 일정회 반복하고 연결을 종료한다. 그리고 114는
연중 무휴이겠지만 처음 server를 시작하는걸 Listen이라고 한다.
자그럼 TServerSocket을 보자 위의 예는 threadbloking mode의 예이다. 이 경우
TServerSocket의 TServerAcceptThread가 첫 accept를 하는 부분이고,
TServerClientThread는 service를 하는 부분이다. server를 만드는 것은 이
service를 구현 하는 것이다. 그리고 TServerWinSocket이 첫 연결하는 전화기라면
TServerClietnWinSocket이 안내양이 사용하는 전화기다.
그럼 TServerSocket의 실행 순서를 확인해 보자.
1. TServerSocket의 생성
Form이 생성 될때 자동으로 호출되며, FServerSocket := TServerWinSocket.Create;
로 TServerWinSocket을 생성한다.
2. TServerSocket.Active := true로 설정
TServerSocket.SetActive(true)를 호출하고, 이함수는 내부에서 FActive := true;
를 지정하고 DoActivate(true)를 호출한다.
DoActivate(true)는 TServerWinSocket.Listen(FHost,FAddress,FService,FPort,5)를
호출한다.
TServerWinSocket.Listen은 TCustomWinSocket.Listen(FHost,FAddress,FService,
FPort,5)를 호출하고, FServerAcceptThread := TServerAcceptThread.Create를
생성한다.
TCustomWinSocket.Listen은 FSocket := socket(PF_INET,SOCK_STREAM,IPPROTO_IP)로
실재 WinSock32를 호출하고, InitSocket(FHost,FAddress,FService,FPort,False)에
서 TSockAddrIn를 구한다. DoSetASyncStyle에서 WSAAsyncSelect(FSocket,0,0,
Longint(Byte(FAsyncStyles)))와 ioctlsocket(FSocket,FIONBIO,0)를 WinSock32
함수를 호출하고 Event(Self,seListen)를 호출한다.
TCustomWinSocket.Event(Self,seListen)은 FOnListen(Self,Socket)에 등록된 event
handler를 출한다.
3. TServerAcceptThread의 Execute 실행
TServerWinSocket.Accept(FServerSocket.SocketHandle)를 계속 실행한다.
TServerWinSocket.Accept는 getsockopt(INVALID_SOCKET,SOL_SOCKET,SO_OPENTYPE,
PChar(@OldOpenType),Len)로 시작하고, setsockopt(INVALID_SOCKET,SOL_SOCKET,
SO_OPENTYPE,PChar(@OldOpenType),Len)로 끝낸다 즉 OpenType을 보관해 놓았다가
다시 되 돌린다. 그 사이에서는 setsockopt(INVALID_SOCKET,SOL_SOCKET,
SO_OPENTYPE,PChar(@SO_SYNCHRONOUS_NONALERT),Len)로 setting을 변경하고,
ClientWinSocket := WinSock.accept(Socket, @Addr, @Len)인 WinSock32함수를
호출해 실재 connection을 기다리게 된다. 그리고 ClientSocket :=
GetClientSocket(ClientWinSocket)을 호출해 TServerClientWinSocket을 얻고
FOnSocketEvent(Self, ClientSocket, seAccept)로 지정된 event handler를 호출
하고 ClientSocket.ASyncStyles := []를 지정하고 GetServerThread(ClientSocket)
로 TServerClinetThread를 얻는다.
GetClientSocket은 FOnGetSocket(Self,Socket,Result)로 지정된 event handler로
TServerClinetWinSocket을 얻을려고 시도해보고,
안되면 TServerClientWinSocket.Create(Socket, Self)로 직접 생성한다.
GetServerThread는 먼저 FActiveThreads에서 ClientSocket = nil인 즉 현재
service를 않하는 TServerClientThread를 얻어보고, 만일 있으면
TServerClientThread.ReActivate(ClientSocket)를 호출한다. 만일 현재 노는게
없으면 FOnGetThread(Self,ClientSocket,Result)로 지정된 event handler를 호출
해서 얻도록하고 안되면, DoCreateThread(ClientSocket)로 직접 생성한다.
DoCreateThread은 TServerClientThread.Create를 호출해 생성하고,
TServerClientThread.Create내에서 ReActivate(ASocket)을 호출한다. 즉 어떻게
한던 TServerClientThread.ReActivate(ClientSocket)은 항상 호출 된다.
ReActivate은 TServerClientWinSocket과 TServerWinSocket를 저장하고
TServerWinSocket.AddThread(Self)로 자신을 등록하고,
TServerClientWinSocket.OnSocketEvent := HandleEvent과
TServerClientWinSocket.OnErrorEvent := HandleError의 event handler를 연결
한다. 마지막으로 Windows.SetEvent(Handle)를 호출한다.
4. TServerClientThread의 Execute 실행
TServerWinSocket.ThreadStart(Self)로 FOnThreadStart(Self,AThread)인 등록된
event handle을 맨 먼제 호출하고, TServerWinSocket.ThreadEnd(Self)로
FOnThreadEnd(Self, AThread)인 등록된 event handler를 마지막으로 호출한다.
그리고 IF StartConnect THEN ClientExecute;
IF EndConnect THEN Break;
를 무한 반복한다.
StartConnect는 WaitForSingleObject(Handle,INFINITE)로 기다리다. terminated를
확인하여 terminate가 않ㄷㅚㅆ으면 true를 돌려준다.
EndConnect는 terminate이고 KeepInCache가 아니면 true를 돌려준다.
ClientExecute는 IF select(0, @FDSet, nil, nil, @TimeVal) > 0로 읽을게 있으면
TServerClientWinSocket.ReceiveBuf(FDSet, -1)를 읽어봐 읽어지면
Synchronize(DoRead)로 TServerWinSocket.ClinetEvent(seRead)를 호출하고
ClinetRead를 거처 FOnClinetRead에 등록된 event handler를 호출한다.
그리고 IF select(0, nil, @FDSet, nil, @TimeVal) > 0로 읽을게 있으면
TServerClientWinSocket.ReceiveBuf(FDSet, -1)를 읽어봐 쓸게있으면
Synchronize(DoWrite)로 TServerWinSocket.ClinetEvent(seWrite)를 호출하고
ClinetWrite를 거처 FOnClinetWrite에 등록된 event handler를 호출한다.
5. Window가 message를 보내오는 경우
TServerClientWinSocket.OnSocketEvent
TServerClientThread.HandleEvent(Sender, Socket, SocketEvent)
Event(SocketEvent)
TServerWinSocket.ClientEvent(Self, ClientSocket, SocketEvent)
ClientConnect(Socket)
OR ClientDisconnect(Socket)
OR ClientRead(Socket)
OR ClientWrite(Socket)
FOnClientConnect(Socket)
OR FOnClientDisconnect(Socket)
OR FOnClientRead(Socket)
OR FOnClientWrite(Socket)에 등록된 event handler를 호출한다.
TServerClientWinSocket.OnErrorEvent
TServerClientThread.HandleError(Sender,Socket,ErrorEvent,ErrorCode)
Error(ErrorEvent, ErrorCode)
TServerWinSocket.ClientError(Sender,Socket,ErrorEvent,ErrorCode)
ClinetErrorEvent(Socket, ErrorEvent, ErrorCode)
FOnClinetError(Socket, ErrorEvent, ErrorCode)
에 등록된 event handler를 호출한다.
6. 참고
indent는 호출되어지는 순서를 나타낸다.
TServerSocket.Create
FServerSocket := TServerWinSocket.Create;
TServerSocket.Active := true;
TServerSocket.SetActive(true);
FActive := true;
DoActivate(true);
TServerWinSocket.Listen(FHost, FAddress, FService, FPort, 5);
TCustomWinSocket.Listen(FHost, FAddress, FService, FPort, 5);
FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
SockAddrIn := InitSocket(FHost, FAddress, FService, FPort, False);
TSockAddrIn.sin_family := PF_INET;
TSockAddrIn.sin_addr := {LookupName(FHost)
OR inet_addr(PChar(FAddress))
OR INADDR_ANY
};
TSockAddrIn.sin_port := {htons(LookupService(FService))
OR htons(FPort)
};
DoSetASyncStyles;
IF AsyncStyles
WSAAsyncSelect(FSocket, CM_SOCKETMESSAGE, Handle,
Longint(Byte(FAsyncStyles)));
ELSE
WSAAsyncSelect(FSocket, 0, 0, Longint(Byte(FAsyncStyles)));
ioctlsocket(FSocket, FIONBIO, 0);
Event(Self, seListen);
FOnListen(Self, Socket);
FServerAcceptThread := TServerAcceptThread.Create(False, Self);
TThread.Create(False);
....
TServerAcceptThread.Execute;
TServerWinSocket.Accept(FServerSocket.SocketHandle);
getsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
PChar(@OldOpenType),Len);
IF stThreadBlocking
setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
PChar(@SO_SYNCHRONOUS_NONALERT), Len);
ClientWinSocket := WinSock.accept(Socket, @Addr, @Len);
ClientSocket := GetClientSocket(ClientWinSocket);
{FOnGetSocket(Self, Socket, Result)
OR TServerClientWinSocket.Create(Socket, Self)
};
FOnSocketEvent(Self, ClientSocket, seAccept);
IF stThreadBlocking
ClientSocket.ASyncStyles := [];
GetServerThread(ClientSocket);
{SELECT TServerClientThread
FROM FActiveThreads
WHERE ClientSocket = nil
TServerClientThread.ReActivate(ClientSocket);
TServerClientWinSocket := ASocket;
TServerWinSocket := FClientSocket.ServerWinSocket;
TServerWinSocket.AddThread(Self);
FActiveThreads.Add(Self);
TServerClientWinSocket.OnSocketEvent := HandleEvent;
TServerClientWinSocket.OnErrorEvent := HandleError;
FEvent.SetEvent;
Windows.SetEvent(Handle);
OR FOnGetThread(Self, ClientSocket, Result)
OR DoCreateThread(ClientSocket)
TServerClientThread.Create(False, ClientSocket);
ReActivate(ASocket); <- 상동
};
setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
PChar(@OldOpenType), Len);
...
TServerClientThread.Execute;
TServerWinSocket.ThreadStart(Self);
FOnThreadStart(Self, AThread);
WHILE
IF StartConnect
WaitForSingleObject(Handle, INFINITE)
RETURN NOT terminated
ClientExecute;
FD_ZERO(FDSet);
FD_SET(TServerClientWinSocket.SocketHandle, FDSet);
TimeVal.tv_sec := 0;
TimeVal.tv_usec := 500;
IF select(0, @FDSet, nil, nil, @TimeVal) > 0
IF TServerClientWinSocket.ReceiveBuf(FDSet, -1) = 0
Break
ELSE
Synchronize(DoRead);
TServerWinSocket.ClinetEvent(seRead);
ClinetRead
FOnClinetRead
IF select(0, nil, @FDSet, nil, @TimeVal) > 0
Synchronize(DoWrite);
TServerWinSocket.ClinetEvent(seWrite);
ClinetWrite
FOnClinetWrite
IF EndConnect
RETURN terminated AND NOT KeepInCache
Break;
TServerWinSocket.ThreadEnd(Self);
FOnThreadEnd(Self, AThread);
...
TServerClientWinSocket.OnSocketEvent;
TServerClientThread.HandleEvent(Sender, Socket, SocketEvent);
Event(SocketEvent);
TServerWinSocket.ClientEvent(Self, ClientSocket, SocketEvent);
{ClientConnect(Socket)
OR ClientDisconnect(Socket)
OR ClientRead(Socket)
OR ClientWrite(Socket)
};
{FOnClientConnect(Socket)
OR FOnClientDisconnect(Socket)
OR FOnClientRead(Socket)
OR FOnClientWrite(Socket)
};
...
TServerClientWinSocket.OnErrorEvent;
TServerClientThread.HandleError(Sender,Socket,ErrorEvent,ErrorCode);
Error(ErrorEvent, ErrorCode);
TServerWinSocket.ClientError(Sender,Socket,ErrorEvent,ErrorCode);
ClinetErrorEvent(Socket, ErrorEvent, ErrorCode);
FOnClinetError(Socket, ErrorEvent, ErrorCode);