달력

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

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 웽웽