Overlapped IO 기반 에코 서버의 구조
- accept 함수를 통해서 client 연결 요청을 받음
- 연결 요청이 없으면 aceept가 blocking 상태가 되니 리스닝 소켓을 non-blocking 소켓으로 변경
- 연결 요청이 없는 상태에서 accept 시 INVALID_SOCKET 에러가 반환되는데 accept에 에러가 없으면 WSAGetLastError 호출 시 WSAEWOULDBLOCK가 반환
- 새로운 클라이언트 연결 요청이 들어오면 WSAOverlapped 구조체를 생성해서 Recv 호출
- Overlapped 구조체는 recv, send시 completion routine에 전달되기때문에 그 안에 event 오브젝트에 정보를 담음
typedef struct
{
SOCKET hClntSock;
char buf[BUF_SIZE];
WSABUF wsaBuf;
} PER_IO_DATA, *LPPER_IO_DATA;
- WSARecv에 수신이 완료됐을 때 호출될 CompletionRoutine을 등록
{
int mode=1;
hLisnSock=WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
ioctlsocket(hLisnSock, FIONBIO, &mode); // 소켓을 non-blocking 모드로 변경
while(1)
{
SleepEx(100, TRUE); // 쓰레드를 주기적으로 alertable wait 상태로 변경
hRecvSock=accept(hLisnSock, (SOCKADDR*)&recvAdr,&recvAdrSz);
if(hRecvSock==INVALID_SOCKET)
{
if(WSAGetLastError()==WSAEWOULDBLOCK)
continue;
else
printf("accept() error");
}
printf("Client connected.....");
lpOvLp=(LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED));
memset(lpOvLp, 0, sizeof(WSAOVERLAPPED));
hbInfo=(LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
hbInfo->hClntSock=(DWORD)hRecvSock;
(hbInfo->wsaBuf).buf=hbInfo->buf;
(hbInfo->wsaBuf).len=BUF_SIZE;
lpOvLp->hEvent=(HANDLE)hbInfo;
WSARecv(hRecvSock, &(hbInfo->wsaBuf),
1, &recvBytes, &flagInfo, lpOvLp, ReadCompRoutine);
}
}
- 수신용 Completion Routine에서는 데이터를 전송한 소켓에 대한 정보와 데이터를 확인해서 연결을 끊거나 다시 그 소켓에 WSASend
- WSASend에도 전송이 완료됐을 때 호출될 CompletionRoutine을 등록
void CALLBACK ReadCompRoutine(
DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
LPPER_IO_DATA hbInfo=(LPPER_IO_DATA)(lpOverlapped->hEvent);
// 구조체 주소를 형변환하여 정보로 활용
SOCKET hSock=hbInfo->hClntSock;
// 수신 완료된 소켓
LPWSABUF bufInfo=&(hbInfo->wsaBuf);
// 수신받은 데이터 정보
DWORD sentBytes;
if(szRecvBytes==0)
{
closesocket(hSock);
free(lpOverlapped->hEvent); free(lpOverlapped);
printf("Client disconnected.....");
} // EOF 전달 시 close
else
{
bufInfo->len=szRecvBytes;
WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);
} // 수신 정보로 다시 Send
}
- 전송용 Completion Routine에서는 다시 WSARecv를 호출해서 수신을 대기
void CALLBACK WriteCompRoutine(
DWORD dwError, DWORD szSendBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
LPPER_IO_DATA hbInfo=(LPPER_IO_DATA)(lpOverlapped->hEvent);
SOCKET hSock=hbInfo->hClntSock;
LPWSABUF bufInfo=&(hbInfo->wsaBuf);
DWORD recvBytes;
int flagInfo=0;
WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}
- 클라이언트에서는 send 후 recv 시 보낸 send 크기만큼 recv를 해줘야 함
{
while(1)
{
strLen=strlen(message);
send(hSocket, message, strLen, 0);
readLen=0;
while(1)
{
readLen+=recv(hSocket, &message[readLen], BUF_SIZE-1, 0);
if(readLen>=strLen)
break;
}
message[strLen]=0;
printf("Message from server: %s", message);
}
}
IOCP 모델
- Overlapped IO 모델은 넌 블로킹 모드의 accpet 함수와 반복된 SleepEx 함수 호출은 성능에 영향을 미칠 수 있음
- 그 문제를 해결하기 위해 accept 호출을 main에서 하고 별도의 쓰레드로 클라이언트와 입출력을 수행
- 위와 같은 구조의 모델이 IOCP 모델
'Programming > Network' 카테고리의 다른 글
열혈 TCP/IP 소켓 프로그래밍 정리(完) (0) | 2021.04.27 |
---|---|
열혈 TCP/IP 23-2. IOCP의 단계적 구현 (2) | 2021.04.25 |
열혈 TCP/IP 22-2. Overlapped IO에서의 입출력 완료의 확인 (0) | 2021.04.25 |
열혈 TCP/IP 22-1. Overlapped IO 모델의 이해 (0) | 2021.04.25 |
열혈 TCP/IP 21-2. 비동기 Notification IO 모델의 이해와 구현 (0) | 2021.04.13 |