0. 용어 정리
(1) 블로킹(Blocking)
recvBuffer에 데이터를 넣음 -> 완료시 반환
버퍼에 데이터가 쌓이는 동안 작업 불가(Busy Waiting).
(2) 논 블로킹(Non-Blocking)
recvBuffer에 데이터를 넣음 -> 완료 확인 요청이 오면 즉시 반환
중간에 다른 작업 가능.
반환이 주기적으로 발생하므로 예외 체크를 해줘야함 -> 코드 복잡화, 자원 소모
(3) 동기(Synchronous)
확인 요청이 오면 즉시 반환.
(4) 비동기(ASynchronous)
확인 요청을 지금 해도 완료가 되면 반환.
Overlapped 모델은 비동기 / 논 블로킹인 우하단 사진과 같다.
1. overlapped 모델
비동기식 논-블로킹 모델의 형태를 띈다.
(1) 순서
- overlapped 함수(WSARecv, WSASend)를 실행한다.
- 함수의 성공 여부를 확인한다.
- 성공했다면 결과를 얻어서 처리한다.
- 실패했다면 이유를 확인한다.
(2) 코드
// overlapped 모델
const int32 BUF_SIZE = 1000;
struct Session
{
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUF_SIZE] = {};
int32 recvBytes = 0;
WSAOVERLAPPED overlapped = {};
};
overlapped 모델에서는 overlapped를 체크하기 위한 코드가 세션 구조체에 포함된다.
이후 서버의 Listen 까지는 동일하다.
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;
}
이전 select 모델에서 클라이언트 소켓을 만들어 accept 하기 이전에 select 또는 event를 감지해서 처리했던 것과는 다르게 여기에서는 먼저 소켓을 만들고 시작한다.
이후에는 이벤트를 사용하거나 콜백을 사용하는 방법이 있다.
(2-1) 이벤트 방법
Session session = Session{ clientSocket };
WSAEVENT wsaEvent = ::WSACreateEvent();
session.overlapped.hEvent = wsaEvent;
cout << "클라이언트 연결됨." << endl;
세션과 이벤트를 생성하고, 이벤트를 세션의 overlapped.hEvent에 연결한다(차이점).
while (true)
{
WSABUF wsaBuf;
wsaBuf.buf = session.recvBuffer;
wsaBuf.len = BUF_SIZE;
DWORD recvLen = 0;
DWORD flags = 0;
// 소켓, 버퍼 주소, 버퍼 갯수, 플래그 주소, 오버렙드 주소, 콜백 주소
if (::WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, &session.overlapped, nullptr) == SOCKET_ERROR)
{
// 아직 데이터를 처리 중
if (::WSAGetLastError() == WSA_IO_PENDING)
{
// 작업
// recv 이벤트 발생
::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, FALSE);
// 이벤트 결과 가져옴
::WSAGetOverlappedResult(session.socket, &session.overlapped, &recvLen, FALSE, &flags);
}
else
{
// TODO
break;
}
}
이제 버퍼를 가져와 사용할 것이다. 여기에서는 WSABUF 형태의 버퍼를 사용한다.
이전에서 사용했던 WSA 함수들을 사용하여 이벤트를 기다리고 결과를 가져오는 작업을 실시한다.
select 모델에서는 상황을 먼저 체크(WSAWaitForMultipleEvents) 후에 recv를 했다면
해당 모델에서는 WSARecv 후에 상황을 체크했다는 차이점이 있다.
이벤트를 체크하는 시간을 WSA_INFINITE가 아니라 짧게 조정해주면 busy waiting을 방지할 수 있다.
cout << "Data Recv = " << session.recvBuffer << endl;
최종적으로 데이터를 출력하면 끝이다.
(2-2) 콜백 방법
struct Session
{
WSAOVERLAPPED overlapped = {};
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUF_SIZE] = {};
int32 recvBytes = 0;
};
overlapped를 반드시 세션의 최상단에 위치시킨다.
void CALLBACK recvCallback(DWORD error, DWORD recvLen, LPWSAOVERLAPPED overlapped, DWORD flags)
{
cout << "Data Recv Len Callback = " << recvLen << endl;
// TO-DO
}
이어서 콜백 함수를 만든다. 콜백 함수의 형태는 고정되어 있다.
// 세션 생성
Session session = Session{ clientSocket };
세션과 이벤트를 연결하는 부분의 코드를 제거한다.
cout << "클라이언트 연결됨." << endl;
while (true)
{
WSABUF wsaBuf;
wsaBuf.buf = session.recvBuffer;
wsaBuf.len = BUF_SIZE;
DWORD recvLen = 0;
DWORD flags = 0;
// 소켓, 버퍼 주소, 버퍼 갯수, 플래그 주소, 오버렙드 주소, 콜백 주소
if (::WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, &session.overlapped, RecvCallback) == SOCKET_ERROR)
{
// 아직 데이터를 처리 중
if (::WSAGetLastError() == WSA_IO_PENDING)
{
// Alertable Wait
::SleepEx(INFINITE, TRUE);
}
else
{
// TODO
break;
}
}
WSARecv 함수의 마지막 nullptr 부분을 콜백 함수(포인터 형태임)로 바꿔준다.
Alertable Wait(사실상 반수면 상태)에 의해 대기하다가 recv가 완료되면 RecvCallback을 호출하게 된다.
세션 구조체의 overlapped를 제일 위로 올린 이유는 WSARecv 함수에 overlapped 포인터를 넘겨줬을 때 다른 정보 또한 넘기기 위해서이다.
void CALLBACK RecvCallback(DWORD error, DWORD recvLen, LPWSAOVERLAPPED overlapped, DWORD flags)
{
cout << "Data Recv Len Callback = " << recvLen << endl;
// TO-DO
Session* session = (Session*)(overlapped);
cout << session->recvBuffer << endl;
}
즉, 콜백 함수 내에서 overlapped를 캐스팅하여 정보를 역으로 꺼내올 수 있다.
(3) 특징
- wsaBuf에 최대 사이즈를 BUF_SIZE로, 버퍼를 session의 recvBuffer로 설정하였기 때문에, 해당 주소가 날아가지 않도록 주의해야 한다.
- 사전에 체크를 하고 recv, send를 하던 상황과 다르게 먼저 recv 후에 상황을 체크하는 방식(비동기)이다.
- 결국 SleepEx를 이용하여 대기하는 방식이기 때문에 단점이 존재한다.
IOCP 모델을 이용하여 Input과 Output을 분리하는 방법이 있다.
'서버 프로그래밍 > 네트워크' 카테고리의 다른 글
2-8. 멀티 스레드 환경에서 발생하는 상황들 (0) | 2023.12.04 |
---|---|
2-7. 소켓 입출력 모델 (3) - IOCP(Input/Output Completion Port) (0) | 2023.12.04 |
2-5. 소켓 입출력 모델 (1) - Select 모델 (Select, WSAEventSelect) (0) | 2023.11.30 |
2-4. 소켓 옵션 설정, 논-블로킹 소켓(Non-Blocking Socket) (0) | 2023.11.30 |
2-3. TCP와 UDP (0) | 2023.11.29 |