Overlapped IO 란? -------------------------------------------------------------------------------------------------------

앞서 비동기화된 알람방식에 대해서 알아봤다면 이번에는 IO작업 자체의 비동기 방식에 대해서 알아볼 차례이다.

비동기 방식의 소켓전송이란 여러개의 소켓이 동시에 IO작업을 처리하는 것을 의미한다.

여러개의 쓰레드나 비동기 시스템을 통해서 여러개의 파일을 동시에 읽어들이는 것과 동일하다고 보면 된다.

시스템 프로그래밍과 어느정도 겹치는 부분이 있지만. 비동기 방식은 다음의 함수들이 필요하다.


1. 비동기 옵션의 소켓

2. 비동기로 송수신이 가능한 함수


1.  비동기 옵션의 소켓을 생성하는 함수.

SOCKET WSASocket(

  _In_  int af, -> 프로토콜 체계 정보 전달.

  _In_  int type, -> 소켓의 데이터 전송방식에 대한 정보 전달.

  _In_  int protocol, -> 두 소켓 사이에 사용되는 프로토콜 정보 전달.

  _In_  LPWSAPROTOCOL_INFO lpProtocolInfo, -> 생성되는 소켓의 특성 정보를 담고 있는 WSAPROTOCOL_INFO 구조체 변수의 주소 값 전달,

     필요 업는 경우 NULL 전달.

  _In_  GROUP g, -> 함수의 확장을 위해서 예약되어 있는 매개변수, 따라서 0 전달.

  _In_  DWORD dwFlags -> 소켓의 속성정보 전달.

); 성공시 소켓의 핸들, 실패시 INVALID_SOCKET 반환.


2. 비동기로 송수신이 가능한 함수


2-1. 비동기 송신함수
int WSASend( 
  _In_   SOCKET s, -> 소켓의 핸들 Overlapped IO 속성의 핸들 전달시 Overlapped IO 방식으로 송신한다.
  _In_   LPWSABUF lpBuffers, -> 전송할 데이터 정보를 지니는 WSABUF 구조체 변수들로 이루어진 배열의 주소 값 전달.
  _In_   DWORD dwBufferCount, -> 두 번째 인자로 전달된 배열의 길이정보 전달.
  _Out_  LPDWORD lpNumberOfBytesSent, -> 전송된 바이트 수가 저장될 변수의 주소 값 전달 (이는 잠시 후 별도로 설명)
  _In_   DWORD dwFlags, -> 함수의 데이터 전송특성을 변경하는 경우에 사용, 예로 MSG_OOB를 전달하면 OOB 모드 데이터 전송.
  _In_   LPWSAOVERLAPPED lpOverlapped, 
-> WSAOVERLAPPED 구조체 변수의 주소 값 전달, Event 오브젝트를 사용해서 데이터 전송의 완료를 확인하는 경우에 사용되는 매개변수.
  _In_   LPWSAOVERLAPPED_COMPLETION_ROUTINE 
-> lpCompletionRoutine -> Completion Routine라는 함수의 주소 값 전달. 이것을 통해서도 데이터 전송 완료를 확인이 가능하다.
);

비동기 방식에서는 함수가 반환되는 것이 모든 데이터의 전송을 의미하는 것이 아니다.
데이터는 시시각각 전송되고 완료되므로 최종적인 데이터가 전송됐을때의 상황을 체크하는 것이 가장 중요하며.
다음의 함수를 통해서 현재까지 전송된 함수의 데이터 크기를 확인이 가능하다.
BOOL WSAAPI WSAGetOverlappedResult(
  _In_   SOCKET s, -> Overlapped가 진행중인 소켓의 핸들
  _In_   LPWSAOVERLAPPED lpOverlapped, -> Overlapped가 진행 중인 WSAOVERLAPPED 구조체 변수의 값 전달.
  _Out_  LPDWORD lpcbTransfer, -> 실제 송수신된 바이트 크기를 저장할 변수의 주소 값 전달.
  _In_   BOOL fWait, -> 여전히 IO가 진행중인 상황의 경우 , TRUE 전달시 IO가 완료될 때까지 대기를 하게 되고 , FALSE 전달시 FALSE를 반환하면서 함수를 빠져나온다.
  _Out_  LPDWORD lpdwFlags -> WSARecv함수가 호출된 경우, 부수적인 정보(수신된 메시지가 OOB 메세지인지와 같은)를 얻기 위해 사용된다. 불필요하면 NULL을 전달한다.
);

2-2. 비동기 수신함수
int WSARecv(
  _In_     SOCKET s, -> 소켓의 핸들 Overlapped IO 속성의 핸들 전달
  _Inout_  LPWSABUF lpBuffers, -> 전송할 데이터 정보를 지니는 WSABUF 구조체 변수들로 이루어진 배열의 주소 값 전달.
  _In_     DWORD dwBufferCount, -> 두 번째 인자로 전달된 배열의 길이정보 전달.
  _Out_    LPDWORD lpNumberOfBytesRecvd, -> 전송된 바이트 수가 저장될 변수의 주소 값 전달 (이는 잠시 후 별도로 설명)
  _Inout_  LPDWORD lpFlags, -> 함수의 데이터 전송특성을 지정하거나 수신하는 경우에 사용된다..
  _In_     LPWSAOVERLAPPED lpOverlapped, ->WSAOVERLAPPED 구조체 변수의 주소 값 전달
  _In_     LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 
-> Completion Routine라는 함수의 주소 값 전달. 이것을 통해서도 데이터 전송 완료를 확인이 가능하다.
);

3. 오류의 확인
만약 비동기 방식으로 진행중이던 작업이 오류가 난다면 다음의 함수를 통해서 확인이 가능하다.
int WSAGetLastError(void);
-> 오류 상황에 대한 상태 값(오류의 원인을 알리는 값) 반환.
비동기 방식은 항상 오류에 대한 값이 반환된다.
항상 모든 데이터가 전송되는것이 아니라 끊겨서 전송되기 때문이다.
아직 데이터가 모두 전송되지 않은 상태를 위의 함수에서는 WAIT_IO_COMPLETION으로 반환하며 그 이외의 상황에 대한 처리를 해주면 될 것이다.

IOCP란? -------------------------------------------------------------------------------------------------------


소켓통신이란 일반적으로 정해진 통신규약이고 운영체제도 그에 따르고 있다. 

즉 윈도우와 리눅스방식이 현재까지 크게 다르지 않았던 이유는 운영체제의 특징적인 부분보다는 기본적인 규격을 지키는 방식을 사용했기 때문이다.

하지만 운영체제란 분명히 그에 맞는 특색이 있고 특색에 따라서 더 많은 것을 지원해 줄수 있을 것이다.


IOCP란 윈도우에서 지원해주는 비동기 통신방식을 의미한다. 설명을 하자면 다음과 같은 방식을 가지고 있다.


1. 비동기방식을 사용하는 소켓을 다수 생성한다.

2. 쓰레드를 통해서 소켓에 발생하는 이벤트를 병렬 관찰 한다.

3. 각각의 소켓은 비동기로 입출력을 실행한다. 


종합 -> 즉 다수의 소켓이 다수의 쓰레드에 의해서 관리 받으며 비동기로 데이터 송수신을 하게 되는 구조를 의미한다.


* 비동기 모드의 서버소켓 구성하기


accept함수는 현재까지 동기화구조로 구성되어 있었다. 하지만 이 서버 소켓도 비동기화 방식으로 구현이 가능하다.

비동기 소켓의 생성 방법은 WSASocket으로 생성을 하면 하지만. ioctlsocket함수를 통해서 실제 비동기 방식의 옵션을 켜줘야 한다.


int mode = 1;


hLisnSock = WSASocket(함수 호출)


ioctlsocket(hLisnSock, FIONBIO, &mode)


의미를 보면 다음과 같다.

"서버소켓 hLisnSock의 FIONBIO(입출력모드)를 mode의 주소에 저장된 값의 형태로 변경한다."

"mode의 값이 0이 아닌 수라면 두번째 인자값의 기능을 on시킨다고 보면 된다."


* IOCP방식으로 소켓을 관리할 포트 생성하기


포트가 무엇이냐면 소켓이 10개 있다고 했을때 윈도우 차원에서 이 소켓을 관리해줄 오브젝트를 생성하는 것을 의미한다.

다음의 함수를 통해서 생성이 가능하다.


HANDLE WINAPI CreateIoCompletionPort(

  _In_        HANDLE FileHandle, -> CP 오브젝트 생성시에는 INVALID_HANDLE_VALUE 전달

  _In_opt_  HANDLE ExistingCompletionPort, -> CP 오브젝트 생성시에는 NULL 전달.

  _In_        ULONG_PTR CompletionKey, -> CP 오브젝트 생성시에는 0 전달.

  _In_        DWORD NumberOfConcurrentThreads -> CP오브젝트에 할당되어 완료된 IO를 처리할 쓰레드의 수를 전달한다.

); 성공시 CP오브젝트 핸들 반환 실패시 NULL 반환


생성시라고 적혀있는 이유는 이 함수가 CP오브젝트와 소켓을 연결하는 역할로도 사용되기 때문이다.


HANDLE WINAPI CreateIoCompletionPort(

  _In_        HANDLE FileHandle, -> CP 오브젝트에 연결할 소켓의 핸들 전달.

  _In_opt_  HANDLE ExistingCompletionPort, -> 소켓과 연결할 CP 오브젝트 전달.

  _In_        ULONG_PTR CompletionKey, -> 완료된 IO관련 정보의 전달을 위한 매개변수,

  _In_        DWORD NumberOfConcurrentThreads -> 어떠한 값이 전달되건, 이 함수의 두번째 매개변수가 NULL이 아니라면 무시된다.

); 성공시 CP오브젝트의 핸들, 실패시 NULL 반환


세번째 정보는 IO는 CP 오브젝트와 그와 연결된 함수를 체크하는 항목과도 관련되어 있으니 확인해보도록 하자.


BOOL WINAPI GetQueuedCompletionStatus(

  _In_   HANDLE CompletionPort, -> 완료된 IO정보가 등록되어있는 CP오브젝트의 핸들 전달.

  _Out_  LPDWORD lpNumberOfBytes, -> 입출력 과정에서 송수된 데이터의 크기 정보를 저장할 변수의 주소 값 전달.

  _Out_  PULONG_PTR lpCompletionKey, 

-> CreateIoCompletionPort변수에서 연결 방식으로 사용할때의 세번째 변수의 값을 반환받을 변수의 주소 값 전달.

  _Out_  LPOVERLAPPED *lpOverlapped, 

-> WSASend, WSARecv 함수 호출시 전달하는 OVERLAPPED 구조체 변수의 주소 값이 저장된 변수의 주소 값 전달.

  _In_   DWORD dwMilliseconds 

-> 타임 아웃 전달, 여기서 지정한 시간이 완료되면 FALSE를 반환하면서 함수를 빠져나간다. 

INFINITE를 전달하면 완료된 IO가 CP오브젝트에 등록될 때까지 블로킹 상태에 있게 된다.

);


IOCP 과정을 요약해 보자면 다음고 같다.

1. CreateIoCompletionPort함수로 포트를 생성하고 그안에 소켓을 연결한다.

2. CreateIoCompletionPort함수로 생성된 포트로 관리하고 싶은 소켓을 연결한다.

3. GetQueuedCompletionStatus함수로 소켓들중 상태에 변화가 있는 소켓을 반환받아서 그와 관련된 작업을 한다.

Posted by JJOREG