Overlapped 모델은 동기 방식의 Send, Recv 등의 함수를 사용하던 Select, EventSelect 모델과 달리, 비동기 방식의 함수(WSARecv, WSASend)를 호출하는 모델이다.
그리고 Event 기반이라는 말은 그 비동기 함수가 완료되었을 때 이벤트를 통해 통지받는 방식이다. 다른 완료 통지 방식으로는 Callback 함수 기반이 있다.
블로킹 - 논블로킹 / 동기 - 비동기 차이
정확한 개념을 모르면 블로킹이 동기와 같은 말이고, 논블로킹이 비동기와 같은 말이라고 생각할 수 있다. 그러나 이는 엄연히 다른 뉘앙스의 용어들이다.
'블로킹 - 논블로킹' 은 말 그대로 함수가 작업을 완료할 때 까지 대기하느냐 아니냐의 관점이다.
반면 '동기 - 비동기' 는 함수를 호출한 시점과 실제 작업을 하는 시점이 같느냐(동시냐) 아니냐의 관점이다.
즉 동기 방식이면 무조건 블로킹이고 비동기 방식이면 논블로킹인 것이 아니다. 동기이면서 논블로킹일 수도 있고, 비동기이면서 블로킹일 수 있다.
하지만 블로킹이 동기 방식과 잘 어울리고, 논블로킹이 비동기 방식과 잘 어울리는 것은 사실이다.
함수들
이번엔 저번에 쓴 글인 EventSelcet 모델에서 사용하는 함수와 겹치는 함수가 많다. 참고하시길.
- WSACreateEvent : 이벤트 객체 만들기
- WSARecv, WSASend : 비동기 통신 함수. 매개변수가 일반 send, recv 함수와 다르다.
- SOCKET s: 통신할 소켓
- LPWSABUF lpBuffers: WSABUFF 배열의 주소. WSABUFF는 읽거나 쓸 버퍼와 해당 버퍼의 길이를 담은 struct이다.
- DWORD dwBufferCount: lpBuffers의 배열 길이.
- LPDWORD lpNumberOfBytesRecvd: 만약 비동기 함수가 바로 완료되었을 경우, 읽거나 쓴 데이터의 크기를 반환한다.
- LPDWORD lpFlags: 각종 옵션 Flags.
- LPWSAOVERLAPPED lpOverlapped: WSAOVERLAPPED 객체의 포인터. WSAOVERLAPPED 객체는 이벤트 객체를 포함하는 struct 이다. 그 외에도 여러 정보를 포함하지만 우리가 신경쓸 것은 이벤트 객체 뿐이다.
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine: 비동기 작업이 완료될 경우 호출할 Callback 함수의 포인터이다. 이 글에서는 Event 기반 완료 통지에 대해 다루므로 사용하지 않는다.
- WSAWaitForMultipleEvents : 이벤트가 Signal 되는지 감시하는 함수. 감시하는 동안 블로킹 됨.
- WSAGetOverlappedResult : overlapped 작업의 완료 결과를 얻는 함수.
- SOCKET s : 통신에 사용된 소켓
- LPWSAOVERLAPPED lpOverlapped : 비동기 함수를 호출 할 때 넘겨주었던 WSAOVERLAPPED 객체의 포인터.
- LPDWORD lpcbTransfer : 통신한 바이트 수를 받는 변수.
- BOOL fWait : 통신이 완료될 때 까지 대기할지 여부.
- LPDWORD lpdwFlags : 부가 옵션. 보통 사용 안함.
참고로 여기서 말하는 비동기 함수는 WSARecv, WSASend 말고도 AcceptEx 등 많이 있지만, 여기선 기본적인 동작을 설명하기 위해 상기한 두 함수만 언급하겠다.
방법
위 함수들을 사용해 Overlapped 모델을 어떻게 구현할지 알아보자.
- 소켓 만들기.
- 이벤트 객체 만들기.
- Overlapped 객체 만들기.(2. 에서 만든 이벤트 객체를 넣어서)
- 비동기 함수(WSARecv, WSASend 등) 호출.
- 4. 에서 호출한 비동기 함수가 SOCKET_ERROR를 반환했다면(작업이 즉시 완료되지 않았다면), Pending(비동기 작업이 아직 시작되지 않음) 상태인지 확인하기.
- 만약 Pending 상태라면 WaitForMultipleEvents 함수를 통해 이벤트가 Signal 될 때 까지 대기.
- 이벤트가 Signal 되었다면 (WaitForMultipleEvents 함수가 반환되면) WSAGetOverlappedResult 함수를 사용해 완료 결과를 얻기.
- 얻은 결과를 사용해 적절한 작업 하기.
- 만약 Pending 상태가 아니라면 에러처리 하기.
- 4.에서 호출한 비동기 함수가 성공했다면(즉시 작업이 완료되었다면) 그냥 동기 send 함수와 동일함.
코드
struct Session
{
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUFSIZE] = {};
int32 recvBytes = 0;
WSAOVERLAPPED overlapped = {};
};
// Overlapped IO (비동기 + 논블로킹)
// - Overlapped 함수를 건다 (WSARecv, WSASend)
// - Overlapped 함수가 성공했는지 확인 후
// -> 성공했으면 결과 얻어서 처리
// -> 실패했으면 사유를 확인
// 1) 비동기 입출력 소켓
// 2) WSABUF 배열의 시작 주소 + 개수 // Scatter-Gather
// 3) 보내고/받은 바이트 수
// 4) 상세 옵션인데 0
// 5) WSAOVERLAPPED 구조체 주소값
// 6) 입출력이 완료되면 OS가 호출할 콜백 함수
// WSASend
// WSARecv
// Overlapped 모델 (이벤트 기반)
// - 비동기 입출력 지원하는 소켓 생성 + 통지 받기 위한 이벤트 객체 생성
// - 비동기 입출력 함수 호출 (1에서 만든 이벤트 객체를 같이 넘겨줌)
// - 비동기 작업이 바로 완료되지 않으면, WSA_IO_PENDING 오류 코드
// 운영체제는 이벤트 객체를 signaled 상태로 만들어서 완료 상태 알려줌
// - WSAWaitForMultipleEvents 함수 호출해서 이벤트 객체의 signal 판별
// - WSAGetOverlappedResult 호출해서 비동기 입출력 결과 확인 및 데이터 처리
// 1) 비동기 소켓
// 2) 넘겨준 overlapped 구조체
// 3) 전송된 바이트 수
// 4) 비동기 입출력 작업이 끝날때까지 대기할지?
// false
// 5) 비동기 입출력 작업 관련 부가 정보. 거의 사용 안 함.
// WSAGetOverlappedResult
while (true)
{
SOCKADDR_IN clientAddr;
int32 addrLen = sizeof(clientAddr);
SOCKET clientSocket;
while (true)
{
clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket != INVALID_SOCKET)
break;
if (::WSAGetLastError() == WSAEWOULDBLOCK)
continue;
// 문제 있는 상황
return 0;
}
Session session = Session{ clientSocket };
WSAEVENT wsaEvent = ::WSACreateEvent();
session.overlapped.hEvent = wsaEvent;
cout << "Client Connected !" << endl;
while (true)
{
WSABUF wsaBuf;
wsaBuf.buf = session.recvBuffer;
wsaBuf.len = BUFSIZE;
DWORD recvLen = 0;
DWORD flags = 0;
if (::WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, &session.overlapped, nullptr) == SOCKET_ERROR)
{
if (::WSAGetLastError() == WSA_IO_PENDING)
{
// Pending
::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, FALSE);
::WSAGetOverlappedResult(session.socket, &session.overlapped, &recvLen, FALSE, &flags);
}
else
{
// TODO : 문제 있는 상황
break;
}
}
cout << "Data Recv Len = " << recvLen << endl;
}
::closesocket(session.socket);
::WSACloseEvent(wsaEvent);
}
평가
Event 기반 Overlapped모델은 EventSelect모델과 마찬가지로 WaitForMultipleEvents 함수를 사용하므로 EventSelect모델과 같은 단점이 있다.
이벤트가 Signal 될 때 까지 대기해야하며, 한 번에 64개의 이벤트만 감시할 수 있는 등의 단점들이 있다.
그냥 이벤트 기반으로 뭔가 한다는게 비효율적인 것 같다.
'네트워크, 서버' 카테고리의 다른 글
NonBlocking Socket (0) | 2022.11.07 |
---|---|
소켓 프로그래밍 (0) | 2022.11.02 |
JobTimer (0) | 2022.07.19 |
JobQueue (0) | 2022.07.16 |
Google Protocol Buffer (0) | 2022.07.07 |