달력

4

« 2024/4 »

  • 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
2014. 7. 7. 12:41

140707_IOCP 예제 분석 DATA_OLD/Server Study2014. 7. 7. 12:41

CTestSession     - CPacketSession을 상속받은 클래스.

접속한 클라이언트 각각의 개체를 관리하는 클래스

소켓을 받아서 Accept

즉, 접속 성공 시 / 접속 종료 시 처리해야 할 작업들이 존재.



CTestIOCP     - CIocp 클래스를 상속받은 클래스.

private:

CTestSession *mListenSession                    // 서버의 listen을 담당할 개체.

CTestSession *mTestSessions[MAX_NUM]   // 접속한 개체들을 관리하는 배열.

:
Posted by 웽웽

IOCP란?

실제 서버에서 사용하는 소켓 IO 관리 기법. 소켓 뿐 아니라 일반 파일에도 적용 가능. 

IO가 발생하는 것을 WorkerThread들이 감지하여 처리하는 방식. 한 스레드가 작업을 처리하는 동안 다른 작업도 처리하기 위해 WorkerThread들은 ThreadPool이라는 기법을 통해 제작됨.

이벤트 방식은 초당 64개 이상의 처리를 하기 힘들고, 하나의 메시지 처리 시에 많은 리소스 소모.

IOCP는 입/출력 핸들이 작업을 시켜놓으면 IOCP가 나중에 결과를 알려주는 비동기 처리 방식.

즉, 메인 프로세스는 입/출력 진행 과정을 알 필요 없이, 해당 작업의 결과만을 통보받는다는 의미.


완료된 작업의 종류를 구분하기 위해, IOCP 등록 시 DWORD형의 Completion Key를 같이 등록.

Queue에 완료된 작업들의 결과를 저장하여 처리하고, 이 Queue를 검사하는 것이 WorkerThread의 역할이다.


CIOCP() - 생성자.

mIOCPHandle    : IOCP 관리 핸들 값. 기본 IOCP 생성 및 소켓 핸들의 IOCP 등록에 사용.

mWorkerThreadCount    : 사용할 WorkerThread 개수.

 mStartupEventHandle    : 시작 관리 이벤트

Begin() - IOCP 사용을 위한 기본 준비. 

- GetSystemInfo()    : 시스템의 정보를 가져오는 함수. SYSTEM_INFO 형식의 변수를 파라미터로 받아서 해당 변수에 시스템의 여러 정보를 멤버로 갖는 구조체를 담는다. 이 중 CPU의 정보를 이용하여 대개 CPU의 2배 만큼의 WorkerThread를 생성한다.


- CreateIoCompletionPort()    : IOCP 핸들 생성 함수. 


- CreateEvent()     : 시작 관리 이벤트 생성. 



- mWorkerThreadVector    : 생성한 WorkerThread들을 관리하는 벡터. for문을 이용하여 CreateThread()로 WorkerThread 생성 후 리턴값으로 받은 해당 스레드의 핸들 값을 push_back()을 통해 벡터에 넣어주고 WaitForSingleObect를 통해 스레드가 완전히 생성될때까지 대기한다.(아니면 WaitForSingleObject에 파라미터로 받는 mStartupEventHandle에 이벤트가 발생할때까지 대기?)

     Q. WaitForSingleObject가 정확히 어떨때 어떤 역할을 하는지?



End() - 생성해둔 WorkerThread를 종료하고, mIocpHandle을 초기화하여 WorkerThread들을 관리하던 Vector를 초기화해줌. 

- PostQueuedCompletionStatus()    : 첫번째 파라미터로 받는 IOCP 핸들에 강제적으로 신호를 발생시키는 함수. 2~4번째 파라미터로 받는 데이터를 신호에 담아 그대로 WorkerThread에 보낸다.



RegisterSocketToIocp() - 소켓/파일 핸들을 IOCP에 등록하는 함수. 소켓이나 파일을 등록해두지 않으면 WSARecv, WSASend를 아무리 호출해도 IOCP에서 신호를 찾을 수 없음.

- 이 함수 내에서 이루어지는 CreateIoCompletionPort()함수의 파라미터는 소켓 핸들, 메인 IOCP 핸들, Completion Key등을 넣어주고, 이 리턴값을 다시 메인 IOCP핸들에 덮어쓴다. 이 과정을 통해서 소켓/파일 핸들이 IOCP에 등록된다.

Q. 왜 처음에 기본 IOCP핸들을 선언해주고 다시 메인 핸들에 덮어쓰는지?



WorkerThreadCallback() - GetQueuedCompletionStatus()함수를 통해 받은 파라미터의 모든 것을 판단하고 처리. 

- 어떠한 IO가 발생했을 때 항상 Overlapped가 넘어오게 된다. 윈도우OS 레벨에서 Overlapped 구조체를 메모리에 계속 유지하면서, IO가 발생했을 때 해당 이벤트와 짝지어 넘겨주기 때문에 이를 통해 넘어온 이벤트의 종류를 알 수 있음.


- NetworkSession 클래스에서 Accept, Read, Write 각각에 대한 Overlapped를 만들어두고 그에 대한 IoType을 해당 작업에 맞는 것으로 설정해 둠으로써 어떤 작업의 IO인지를 알 수 있음.


- Overlapped_Ex->Object 필드는 신호를 보낸 접속자를 구분하기 위해 만든 필드. 하나의 접속자마다 NetworkSession 클래스가 1개씩 할당되는데, 이 NetworkSession클래스가 초기화될 때 위에서 언급한 Accept, Read, Write에 대한 각각의 Overlapped클래스의 Object에 this를 넣어줌으로써 어떤 접속자가 보낸 것인지 구분할 수 있다.



- GetQueuedCompletionStatus()    : 대기하다가 큐에서 신호를 가져오는 함수. 입력 파라미터로 사용할 IOCP 핸들 값과 대기 시간을 받고, 출력 파라미터로 발생한 IO의 데이터 크기, 키 값, Overlapped 값을 WorkerThreadCallback() 시작 시 선언한 세 변수에 저장한다. 

이 함수를 통해 받은 값들을 분석하여 각각의 IO 형태에 맞는 가상함수들을 처리해준다.








:
Posted by 웽웽

EventSelect 클래스

클라이언트에서 많이 쓰이는 방식이자 일반적인 소켓 사용 방법.

스레드를 하나 생성하여 그 스레드 안에서 지정한 소켓의 모든 이벤트(Read, Write등)를 검출해내는 구조로 구성.


CEventSelect() - 생성자. 이벤트와 스레드를 관리하는 여러 핸들들과 소켓 핸들을 초기화시킴. 그 중에서도 mSelectEventHandle이 모든 소켓이벤트의 중심이 되는 중요한 역할을 함.

mSelectThreadHandle    : 소켓 이벤트를 관리할 스레드 핸들

mDestroyEventHandle    : 스레드 종료 이벤트 핸들

mStartupEventHandle    : 스레드 시작 이벤트 핸들



Begin() - 소켓 이벤트 관리 스레드 생성 및 WSAEventSelect를 이용해 사용할 소켓 이벤트 등록.

- 소켓 이벤트 관리 이벤트 / 스레드 종료 이벤트 / 스레드 시작 이벤트 /소켓 이벤트 관리 스레드 생성


- WSAEventSelect()    : 사용할 소켓 이벤트들을 등록하여 해당 소켓의 이벤트를 검출함.

해당 소켓에 등록한 이벤트가 발생되었을 경우 mSelectThreadHandle에 SelectThreadCallback함수가 등록된 스레드가 생성되어 해당 이벤트에 대한 처리가 이루어짐.


- WaitForSingleObject()    : 스레드 생성 완료까지 대기 후, 스레드가 Wait될 때까지 시간을 벌어줌. 스레드가 생성되면서 등록한 이벤트 핸들(mStartupEventHandle)에 이벤트가 발생하면 함수 종료.



End() - 이벤트 변수들의 초기화 및 스레드 종료 이벤트를 발생시켜 해당 스레드가 종료될 때까지 대기함.



SelectThreadCallBack() - 소켓 이벤트를 관리하는 스레드. 실제 소켓 관련 이벤트가 발생했을 때 처리하는 스레드. 

- WaitForMultipleObjects    : 임시 핸들 배열에 저장해둔 mDestroyEventHandle과 mSelectEventHandle 두개의 핸들에 이벤트가 발생할 때까지 대기. 이벤트 발생 시, 해당 코드는 EventID에 등록된다. EventID에 들어간 값에 대한 switch문으로 각각의 케이스에 따른 작업 수행.


- WSAEnumNetworkEvents()    : 소켓에 관련된 이벤트가 발생했을 때 해당 이벤트를 받아오는 함수. NETWORKEVENTS 형식인 NetworkEvents 변수에다가 소켓에 발생한 네트워크 이벤트를 받음. 각각의 이벤트에 따라 가상함수를 호출함.

* 여기서 가상함수를 사용하는 이유는, 이 EventSelect 클래스를 그대로 사용하지 않고, 이를 상속받는 클래스들이 각각의 기능에 맞게 해당 부분을 재정의 하여 사용할 수 있도록 하기 위함임.



:
Posted by 웽웽

PacketSession은 NetworkSession클래스를 상속받음. 데이터만 관리하는 NetworkSession을 상속받아 패킷기능을 추가하는 개념.


패킷이란?

NetworkSession을 통해서 주고받는 데이터를 의미있는 덩어리화 한 것?

메모리 블록 방식    : 주고받는 블록의 최대 크기를 지정해두고, 앞부분부터 일정 크기만큼의 데이터에 역할을 정해두는 방식. 


이 방식에서 쓰이는 패킷의 구조

|----------------------------------------4096 bytes-----------------------------------|

|          4Bytes       ||          4Bytes    ||            4Bytes       ||              4084Bytes            |

|-DWORD(Length)-||--DWORD(UN)--||--DWORD(Protocol)-||----------DATA-----------|

     |------------------------Encryption-------------------------------|


Length    : 전체 패킷의 길이 저장. 

Protocol  : 패킷의 기능.

UN         : 패킷 고유 번호. 클라이언트마다 고유한 번호 전송. 해킹 검증 및 동일 패킷 검증에도 사용.

DATA     : 사용자가 정의 한 데이터. 



CPacketSession() - 생성자. 패킷 버퍼와 관련 변수들을 초기화한다.

- mRemainLength    : mPacketBuffer에 남은 길이. 패킷 처리 후 Nagle알고리즘으로 완성되지 않은 패킷의 남은 길이.

- mCurrentPacketNumber    : 현재 상태에서 상대에게 보내야 할 패킷의 번호.

- mLastReadPacketNumber    : 마지막으로 받은 패킷 번호. 이미 보낸 패킷인지 여부를 검증함.


Begin() - 클래스 시작 함수. PacketSession에서 사용할 WriteQueue 및 상속받은 NetworkSession 클래스 초기화. CNetworkSession클래스의 Begin()을 리턴함.


End() - 큐를 종료하고 CNetworkSession클래스의 End()를 리턴함.

- mLastReadPacketInfoVectorForUdp    : UDP는 TCP와 달리 하나의 개체가 여러 접속을 관리하기 때문에 받은 곳의 정보와 패킷 번호를 이 벡터로 관리한다.

_READ_PACKET_INFO : 위의 벡터가 관리하는 구조체.

->RemoteAddress : 받은 주소 / RemortPort : 받은 포트 / PacketNumber : 패킷번호


WriteComplete() - 보내기 완료 시 큐에서 데이터를 삭제하는 함수. 전송 후 pop을 해줌. 


 

TCP코드


GetPacket() - 받은 패킷을 분석하는 함수. 받은 패킷을 Decrypt한 후 패킷의 길이, 번호 등을 얻는다. PacketSession에서 가장 중요한 역할.

-  패킷의 유효성 검사(크기가 버퍼보다 크거나 0보다 작으면 잘못된 패킷)

- 유효한 패킷일 경우 암호화된 부분을 Decrypt해줌.

- 패킷의 번호와 프로토콜을 받아옴(PacketNumber, Protocol).

- 패킷 처리 후, 남은 길이 확인 및 마지막 받은 패킷 번호 저장 등 실행.


ReadPacketForIocp() - CNetworkSession에서 데이터를 받아오는 함수. NetworkSession 클래스 버퍼에서 PacketSession클래스 버퍼로 데이터를 복사해옴. 이렇게 복사해온 데이터를 GetPacket()함수를 이용하여 패킷형태로써 확인을 하는 순서로 흘러감. ReadPacketForEventSelect도 동일한 흐름.


WritePacket() - 전송할 데이터에 길이, 프로토콜, 패킷 번호등을 넣어 패킷화 하여 보내는 함수. 

- 패킷 길이를 지정( 길이 + 패킷번호 + 프로토콜 + 데이터 ).

- 임시버퍼에 순서대로 써줌( 패킷 길이 + 패킷 번호 + 프로토콜 + 데이터 ).

- 길이를 제외한 나머지 부분을 Encrypt해줌.

- 완성된 데이터를 WriteQueue에 Push해줌.

->WriteQueue에 데이터가 저장된 임시 버퍼의 포인터를 Push하고, 그 리턴값으로 Push된 Data의 포인터를 리턴하여 WriteData에게 넣어줌.

- WriteData를 Write함수를 통해 써줌. 왜 이렇게 포인터를 통해 보내주느냐면, 임시버퍼에 써둔 데이터는 WritePacket함수가 종료됨과 동시에 사라지기때문에, WriteComplete가 일어나기전까지 데이터가 무사할 수 있는 큐에 넣는 방식으로 유지시킴.





:
Posted by 웽웽

TCP는 신뢰성을 갖춘 네트워크 프로토콜.

UDP는 TCP의 신뢰성을 포기한 대신 실시간성을 강화(ex. 동영상 스트리밍 등).

대신 직접 코딩하여 수정함으로써 UDP에 TCP의 신뢰성도 추가 가능.



Overlapped는 IOCP를 사용하기 위해 쓰는 구조체.

일어난 이벤트의 속성(파일의 입출력 등)을 구분하는 용도.




Begin() - 개체 시작을 위한 사전 준비. 소켓을 열기 위한 준비 및 버퍼 초기화 등의 작업 수행.


TcpBind() - NetworkSession클래스를 TCP로 사용할 때 호출하는 함수.

- WSASocket    : 소켓 생성. TCP/UDP에 대한 설정.

- getsockopt, setsockopt    : TCP관련 소켓 옵션 컨트롤 함수.

- Nagle 알고리즘    : "가능하면 한번에 많이 보내라" 원칙 기반. ack받고 데이터를 보낸 뒤, 다음 ack이 올때까지 데이터를 모아뒀다 ack를 받으면 한번에 패킷화하여 보내는 방식.

네트워크 효율성이 높아지는 장점 / ACK를 기다려야하는 데서 오는 속도 저하가 단점.

TCP_NODELAY 옵션을 통해 Nagle알고리즘 사용을 켜고 끌 수 있다.


Listen(USHORT port, INT backLog) - TCP를 이용해 생성한 소켓을 Listen.

- bind    : SOCKADDR_IN 구조체에 저장한 주소/포트 등을 소켓과 묶음.

- listen    : bind에 성공한 소켓으로 listen실행. 성공 시, 그때부터 연결 요청을 받을 수 있음.

backLog - 동시 접속 요청 시 최대 대기자 수를 설정하는 값. SOMAXCONN 으로 최대 값으로 설정 가능.

LINGER - 소켓 close시, 데이터 송수신이 종료된 후에 소켓 close가 되게끔 하는 옵션. setsockopt에서 l_onoff=1 설정시 동작함. 안전한 소켓 종료가 장점 / 데이터가 많을 때 늦게 종료될 가능성이 단점.


Connect(LPSTR address, USHORT port) - 클라이언트->서버, 서버->서버간 연결을 위한 함수.

포트, 주소 등의 정보를 네트워크 바이트 방식으로 변환하여 구조체에 넣어줌.

- WSAConnect    : SOCKADDR_IN 구조체에 저장한 접속 대상의 주소/포트를 통해 접속 시도.

접속성공/대기상태 의 경우 성공으로 인식.

QOS - Quality Of Service의 줄임말. 네트워크상에서 데이터 전송 품질을 나타내는 규격. 


Accept(SOCKET listenSocket) - Listen과 짝. 클라이언트의 접속 요청을 허락하는 함수.

WSASocket함수로 미리 accept를 할 소켓을 생성해둠.

- AcceptEX    : 일반적으로 while문을 이용한 WSAAccept는 하나의 accept가 완료되기 전에는 다른 연결에 대한 accept를 할 수 없어서 접속이 몰릴 경우 접속 실패를 일으킬 가능성이 있음. 

이를 방지하기 위해 AcceptEx는 while로 처리하지 않고, 하나의 AcceptEx당 하나의 소켓을 미리 만들어두어 Accept 요청 시에 미리 생성된 소켓을 활용하는 방식으로, 여러개의 AcceptEx를 호출해두어 그만큼 많은 접속 요청이 몰렸을 때를 대비할 수 있음.

또한 연결 수락 후, 초기 read를 한번 수락하는 것을 한번에 진행 가능함.

참고로, 이 함수에서 parameter로 받는 주소 길이는 sizeof(sockaddr_in)보다 16바이트가 더 커야함.


InitializeReadForIOCP() - IOCP를 쓸 때 사용하는 초기 받기 함수. 최초 데이터 수신 후 성공 시 이후 작업 진행.

- WSARecv    : 윈도우즈에서 지원하는 모든 소켓 방식에서 사용가능한 범용 데이터 수신 함수.

이벤트/스레드/콜백/IOCP등 모든 방식에서 WSARev가 데이터 수신 작업 가능. WSABUF 사용.

WSABUF - WSABUF buf, len으로 구성. buf는 CHAR *형태의 버퍼. len은 버퍼의 개수. 이때 mReadBuffer의 포인터를 buf에 넣어준다.

len은 다중 버퍼를 이용한 Scatter/Gather기술(많은 데이터 송수신시 성능 향상)을 위한 변수. 이는 WSASend에서도 사용 가능.


ReadForIOCP() - 초기 받기 실행 후, 실제 데이터를 정해진 버퍼에 복사함. IOCP에서 IO_READ가 발생했을 경우 데이터를 확인할 때 사용.

- CopyMemory(data, mReadBuffer, dataLength)    : WSARecv를 통해 받아온 데이터가 들어있는 mReadBuffer에서 데이터를 data에 복사함.

mReadBuffer은 초기 받기 때 WSARecv의 버퍼로 지정된 것.(InitializeReadForIOCP에서 WSABUF의 buf에 mReadBuffer의 포인터를 넣어줌.)


ReadForEventSelect() - 이벤트 방식의 소켓 핸들링에서 사용. 주로 클라이언트에서 사용하는 방식. WSAEventSelect라는 API를 이용한 방식. 지정 소켓에 상황 발생 시, 이를 이벤트로 알려주는 방식.

- IOCP와 이벤트방식의 차이점

 이벤트 : 데이터가 왔다는 신호 발생 시, WSASRecv를 호출하면 바로 데이터를 읽어올 수 있다.

 IOCP : 미리 WSARecv 호출해두지 않으면 데이터를 받을 수 없음. IOCP에서 신호가 왔을 때 WSARecv로 받는 게 아니라 미리 호출해둔 WSARecv의 버퍼에서 데이터를 확인할 수 있음.

+ 이벤트 방식에서는 WSARecv에 쓰이는 ReadBytes 파라미터에 받은 데이터의 길이가 들어옴.


참고로, Read작업은 IOCP방식과 이벤트방식이 따로 존재하지만, Write는 따로 구분하지 않음.




Write(BYTE *data, DWORD dataLength) - WriteOverlapped를 이용해서 WSASend로 데이터 보냄.

Write를 할 때 넣은 데이터 포인터를 전송 완료시까지 살려두어야 함

-> 데이터를 큐에 넣고, 전송 완료 시 큐에서 삭제하는 형태로 제작.









:
Posted by 웽웽