XI : 소켓 입출력 모델 II
TCP/IP 윈도우 소켓 프로그래밍 을 읽고 정리한 문서입니다 ;)
Overlapped 모델 (I) : Event 기반
Overlapped 모델은 10장에서 배운 소켓 입출력 모델과는 근본적으로 다른 입출력 방식으로 고성능을 제공한다.
원래 Overlapped 입출력 방식은 윈도우 운영체제에서 고성능 파일 입출력을 위해 제공하는데, 이를 소켓 입출력에도 사용할 수 있게 만든 것이 Overlapped 소켓 입출력 모델이다.
- 동기 입출력
- 동기 입출력은 전형적인 입출력 방식으로서, 프로그램은 입출력 함수를 호출한 후 입출력 작업이 끝날 때까지 대기한다. 입출력 작업이 끝나야 입출력 함수가 리턴한다.
- 10장의 Select 소켓 입출력 모델은 모두 동기 입출력 방식으로 소켓 입출력을 처리한다. 다만 입출력을 처리할 시점을 운영체제가 알려주어 효율적으로 개선되는 것이다.
- Select 소켓 입출력 모델은 운영체제가 함수 호출 시점을 알려주므로 비동기 통지 방식으로 볼 수 있다.
- 비동기 입출력
- 응용 프로그램은 입출력 함수를 호출한 후, 이 작업의 완료 여부와 무관하게 다른 작업을 진행할 수 있다.
- 운영체제는 이 입출력 작업이 완료되었을 때 응용프로그램에게 알려준다. 그 때 응용프로그램이 하던 작업을 중단하고 이 결과를 처리하게 된다.
- 비동기 입출력 방식에서는 입출력 완료를 운영체제가 알려주는 개념이 반드시 필요하므로 비동기 통지도 사용된다고 볼 수 있다.
따라서, 기존 Select 모델들은 동기 입출력과 비동기 통지가 결합된 형태이고, Overlapped 기반의 모델은 비동기 입출력과 비동기 통지가 결합된 모델이다.
Overlapped 모델의 공통 절차
- 비동기 입출력을 지원하는 소켓을 생성한다.
socket()
함수로 생성한 소켓은 기본적으로 비동기 입출력을 지원한다.
- 비동기 입출력을 지원하는 소켓 함수를 호출한다.
AcceptEx()
,ConnectEx()
,WSARecv()
,WSASend()
등 총 13개의 함수가 있다.
- 운영체제가 입출력 작업 완료를 응용프로그램에 알려주면, 이를 처리한다.
이후 Overlapped 모델은 운영체제의 비동기 통지 방식에 따라 두 종류로 구분된다.
- 운영체제는 응용프로그램이 등록해둔 이벤트 객체를 신호 상태로 바꾼다. 이를 관찰하던 응용프로그램이 신호를 받아 작업 결과를 받아온다.
- 운영체제는 응용프로그램이 등록해둔 콜백 함수인, 완료 루틴을 자동으로 호출한다.
소켓 응용프로그램에서 Overlapped 모델을 사용하는 주된 이유는 데이터를 보내고 받는 작업을 효율적으로 처리하기 위해서이다. 이 때 사용하는 핵심 함수는 WSASend()
와 WSARecv()
이다.
int WSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
int WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesReceived,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
lpBuffers, dwBufferCount
:WSABUF
구조체 배열의 시작 주소와 배열의 원소 개수이다.lpNumberOfBytesSent, lpNumberOfBytesRead
: 함수 호출이 성공했을 때 보내거나 받은 바이트 수를 저장한다.LPWSAOVERLAPPED lpOverlapped
:WSAOVERLAPPED
구조체의 주소값이다.typedef struct _WSAOVERLAPPED( DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; WSAEVENT hEvent ) WSAOVERLAPPED;
WSAOVERLAPPED
구조체의 처음 네 개는 운영체제가 내부적으로 사용하고, 마지막 변수인hEvent
는 이벤트 객체의 핸들 값으로서, Overlapped 모델(I) 에서 사용한다. 입출력 작업이 완료되면 해당 이벤트 객체는 신호 상태가 된다.LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
: 입출력 작업이 완료되면 운영체제가 자동으로 호출할 완료 루틴 (콜백 함수)의 주소값이다. Overlapped 모델(II)에서 사용한다.
WSASend()
와 WSARecv()
함수는 Scatter/Gather 입출력을 지원한다. 이는 WSABUF
구조체를 이용하여 다수의 버퍼를 쓰기로 보내고, 읽혀진 데이터를 다수의 버퍼로 나누어 저장할 수 있게 해준다.
Overlapped 모델(I) 소켓 입출력 절차
비동기 입출력을 지원하는 소켓을 생성한다. 이 때
WSACreateEvent()
함수를 호출하여 대응하는 이벤트 객체 또한 같이 생성한다.비동기 입출력을 지원하는 소켓 함수를 호출한다. 이 때
WSAOVERLAPPED
구조체의hEvent
변수에 이벤트 객체의 핸들 값을 넣어서 전달한다. 비동기 입출력 작업은 곧바로 완료되지 않을테니WSA_IO_PENDING
이라는 오류 값을 리턴할 것이다.WSAWaitForMultipleEvents()
함수를 호출하여 이벤트 객체가 신호 상태가 되기를 기다린다.비동기 입출력 작업이 완료되어
WSAWaitForMultipleEvents()
함수가 리턴하면,WSAGetOverlappedResult()
함수를 호출해 비동기 입출력 결과를 확인하고 데이터를 처리한다.BOOL WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags );
LPWSAOVERLAPPED lpOverlapped
: 비동기 입출력 함수 호출에 사용했던WSAOVERLAPPED
구조체를 다시 넣는다.LPDWORD lpcbTransfer
: 전송된 바이트 수가 저장된다.BOOL fWait
: 비동기 입출력 작업이 끝날 때까지 대기하려면TRUE
이지만, 이미 완료된 입출력에 대해 결과만 받아오는 것이므로FALSE
를 사용한다.
위의 단계를 반복한다.
Overlapped 모델 (II) : APC 기반
Overlapped 모델(II)은 APC 기반의 완료 루틴을 사용하는 방식이다. 완료 루틴은 운영체제가 적절한 시점에 자동으로 호출하는 사용자 정의 함수를 의미한다.
APC와 완료 루틴
SPC는 synchronous procedure call이라는, 우리가 흔히 볼 수 있는 스택 기반의 함수 호출 방식이다. 하지만 APC는, asynchronous procedure call로서, 다음 형태의 호출이 강제된다.
VOID CALLBACK APCProc(ULONG_PTR dwParam);
이 함수는 CALLBACK
으로 정의되어 있으니, 우리가 직접 호출하지는 않을 것이다. 이러한 APC Procedure를 호출하기 위해서는 QueueUserAPC()
를 호출하면 된다.
DWORD QueueUserAPC(
PAPCFUNC pfnAPC,
HANDLE hThread,
ULONG_PTR dwData
);
PAPCFUNC pfnAPC
: APC Procedure의 함수 포인터를 의미한다.HANDLE hThread
: 실제로 사용자가 정의한 APC Procedure를 어떤 스레드가 호출하게 될지이다.
하지만 이러한 방식으로 호출한다고 해서 저장한 파라미터를 가지고 바로 호출되는 것이 아니다.
사용자가 지정한 스레드가 alertable상태가 될 때 호출된다.
Windows의 스레드들은 그 스레드가 생성되어 파괴될 때까지 자신의 상태를 가지고 있다.
Waiting 상태는 thread가 특정 event를 받기 전까지 아무것도 하지 않는 상태이다. 이는 사용자가 WaitFor...
류나 Sleep
류의 함수를 사용하면 해당 스레드는 Waiting 상태로 진입한다.
Ready 상태는 CPU scheduling 대상이긴 하나 아직 CPU의 quantium time을 받지 못한 상태이다.
Running 상태는 CPU가 현재 이 thread를 수행하고 있는 상태를 의미한다. Thread가 수행되고 있다는 것은 즉 Ready와 Running을 오가며 코드가 수행되는 것이다.
이러한 세 상태는 Windows가 직접적으로 관할하는 상태이다.
하지만 사용자가 조정할 수 있는 유일한 상태인, alertable 상태가 존재한다.
이 상태로 스레드를 진입시키는 방법은 WaitFor...Ex
류, SleepEx
류의 함수를 사용하면 된다.
alertable 상태로 진입한 스레드는 APC Queue에 혹시 Queueing된 정보가 있는지 확인을 하게 된다. 스레드는 이 큐에서 하나의 Node씩 빼와서 지정한 함수를 호출하게 된다.
만일 Queue의 모든 함수 호출을 완료했거나, 지정된 alertable timeout이 지나게 되면, event가 발생되어 alertable 상태에서 ready 상태로 전환되게 된다.
완료 루틴을 이용한 입출력 절차
- 비동기 입출력 함수를 호출함으로서 운영체제에 입출력을 요청한다.
- 해당 스레드는 곧바로 alertable wait 상태에 진입한다.
- 비동기 입출력 작업이 완료되면, 운영체제는 스레드의 APC 큐에 결과를 저장한다.
- alertable wait 상태의 스레드는 APC 큐에 저장된 정보를 참조하여 완료 루틴을 호출한다.
- 모든 완료 루틴 호출이 끝나면 스레드는 alertable wait 상태에서 빠져나온다.
Overlapped 모델 (II) 소켓 입출력 절차
- 비동기 입출력을 지원하는 소켓을 생성한다.
- 비동기 입출력을 지원하는 소켓 함수를 호출한다. 비동기 입출력 작업이 곧바로 완료되지 않을 테니
SOCKET_ERROR
에러 값과WSA_IO_PENDING
이라는 에러 코드가 리턴될 것이다. - 비동기 입출력을 호출한 스레드를 alertable wait 상태로 만든다. 이에는
WaitForSingleObjectEx
,WSAWaitForMultipleEvents
등의 함수가 사용된다. - 비동기 입출력 작업이 완료되면, 운영체제는 완료 루틴을 호출한다. 완료 루틴에서는 비동기 입출력 결과를 확인하고 후속 처리를 한다.
- 완료 루틴 호출이 끝나면 스레드는 alertable wait 상태에서 빠져나온다.
운영체제가 호출하는 완료 루틴의 형태와 각 인자의 의미는 다음과 같다.
void CALLBACK CompletionRoutine(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
);
DWORD dwError
: 비동기 입출력 결과이다. 오류가 발생하면 0이 아닌 값이 된다.DWORD cbTransferred
: 전송 바이트 수이다. 통신 상대가 접속을 종료하면 이 값은0
이 된다.LPWSAOVERLAPPED lpOverlapped
: 비동기 입출력 함수 호출 시 넘겨준WSAOVERLAPPED
구조체의 주소값이다. APC 기반의 모델에서는 이 구조체를 완료 루틴에서 사용할 일은 없다.
'공부한 이야기 > 윈도우 소켓 프로그래밍' 카테고리의 다른 글
X : 소켓 입출력 모델 I (0) | 2023.04.29 |
---|---|
IX : GUI 소켓 응용 프로그램 (0) | 2023.04.29 |
VIII : 소켓 옵션 (0) | 2023.04.29 |
VII : UDP 서버-클라이언트 (0) | 2023.04.29 |
VI : 멀티스레드 (0) | 2023.04.29 |