Overlapped IO : 비동기로 여러 IO 동시 진행
WSASocket() : 마지막 인자에 WSA_FLAG_OVERLAPPED를 전달하면 비동기 API 사용 가능한 소켓 된다. (Non-blocking은 아님.)WSASend() : Overlapped IO에 사용되는 데이터 출력 함수__WSABUF : 두 번째 인자 구조체. 전송 데이터 버퍼와 크기 정보 저장__WSAOVERLAPPED : 여섯 번째 인자 구조체WSARecv() : 데이터 수신WSASend()의 인자 lpNumberOfBytesSent에는 전송 데이터 크기 저장됨
SOCKET_ERROR 반환. WSAGetLastError() 호출해서 WSA_IO_PENDING 확인 가능.WSAGetOverllapedResult()로 전송 데이터 크기 확인 (이 함수는 수신 결과 확인도 가능)Overlapped IO 입출력 완료 및 결과 확인 방법
WSASend(), WSARecv() 6번째 인자 활용. Event 오브젝트 기반WSASend(), WSARecv() 7번째 인자 활용. Completion Routine 기반 if(connect(hSocket, (SOCKADDR*)&sendAdr, sizeof(sendAdr))==SOCKET_ERROR)
ErrorHandling("connect() error!");
evObj = WSACreateEvent();
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = evObj; // Event 오브젝트 등록
dataBuf.len = strlen(msg) + 1;
dataBuf.buf = msg;
// 바로 반환되는데, 에러 뜨면 아직 남아있는 건지 확인하고 Event로 기다리기
if(WSASend(hSocket, &dataBuf, 1, &sendBytes, 0, &overlapped, NULL) == SOCKET_ERROR)
{
if(WSAGetLastError() == WSA_IO_PENDING)
{
puts("Background data send");
WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
WSAGetOverlappedResult(hSocket, &overlapped, &sendBytes, FALSE, NULL);
}
else
{
ErrorHandling("WSASend() error");
}
}
Overlapped Send 예제
// 역시 바로 반환되는데, ERROR 뜨면 PENDING인지 확인. PENDING이면 이벤트 기다리기.
if(WSARecv(hRecvSock, &dataBuf, 1, &recvBytes, &flags, &overlapped, NULL) == SOCKET_ERROR)
{
if(WSAGetLastError()==WSA_IO_PENDING)
{
puts("Background data receive");
WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE);
WSAGetOverlappedResult(hRecvSock, &overlapped, &recvBytes, FALSE, NULL);
}
else
{
ErrorHandling("WSARecv() error");
}
}
Overlapped Recv 예제
Completion Routine 등록
alertable wait 상태 만드는 함수
WaitForSingleObjectEx()WaitForMultipleObjectsEx()WSAWaitForMultipleEvents() memset(&overlapped, 0, sizeof(overlapped));
dataBuf.len = BUF_SIZE;
dataBuf.buf = buf;
evObj = WSACreateEvent(); // Dummy event object
// 마지막 인자에 콜백 함수 등록
if(WSARecv(hRecvSock, &dataBuf, 1, &recvBytes, &flags, &overlapped, CompRoutine)==SOCKET_ERROR)
{
if(WSAGetLastError()==WSA_IO_PENDING)
puts("Background data receive");
}
// 등록한 콜백 함수 실행되도록 alert wait 상태로 넘어가기
idx = WSAWaitForMultipleEvents(1, &evObj, FALSE, WSA_INFINITE, TRUE);
if(idx==WAIT_IO_COMPLETION) // IO가 정상 완료 했다면
puts("Overlapped I/O Completed");
else
ErrorHandling("WSARecv() error");
WSACloseEvent(evObj);
closesocket(hRecvSock);
closesocket(hLisnSock);
WSACleanup();
return 0;
}
// CALLBACK 의미 : 콜백 함수는 운영체제가 호출하므로, 호출 규약을 알려주는 것
void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
if(dwError!=0)
{
ErrorHandling("CompRoutine error");
}
else
{
recvBytes = szRecvBytes;
printf("Received message: %s \n", buf);
}
}
ioctlsocket(hLisnSock, FIONBIO, &mode)
넌-블로킹 모드 소켓
INVALID_SOCKET 바로 반환WSAGetLastError() 호출하면 WSAEWOULDBLOCK 반환#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#define BUF_SIZE 1024
void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void CALLBACK WriteCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void ErrorHandling(char *message);
typedef struct
{
SOCKET hClntSock;
char buf[BUF_SIZE];
WSABUF wsaBuf;
} PER_IO_DATA, *LPPER_IO_DATA;
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hLisnSock, hRecvSock;
SOCKADDR_IN lisnAdr, recvAdr;
LPWSAOVERLAPPED lpOvLp;
DWORD recvBytes;
LPPER_IO_DATA hbInfo;
int mode = 1, recvAdrSz, flagInfo = 0;
if(argc!=2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
// 마지막 인자로 비동기 API 가능한 소켓 생성
hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
// Non-blocking 모드로 바꾼다
ioctlsocket(hLisnSock, FIONBIO, &mode);
memset(&lisnAdr, 0, sizeof(lisnAdr));
lisnAdr.sin_family = AF_INET;
lisnAdr.sin_addr.s_addr = htonl(INADDR_ANY);
lisnAdr.sin_port = htons(atoi(argv[1]));
if(bind(hLisnSock, (SOCKADDR*) &lisnAdr, sizeof(lisnAdr))==SOCKET_ERROR)
ErrorHandling("bind() error");
if(listen(hLisnSock, 5)==SOCKET_ERROR)
ErrorHandling("listen() error");
recvAdrSz = sizeof(recvAdr);
while(1)
{
SleepEx(100, TRUE); // for alertable wait state
hRecvSock = accept(hLisnSock, (SOCKADDR *)&recvAdr, &recvAdrSz);
// 호출 대상 소켓이 Non-blocking 모드이므로, accept() 정말 받은건지 아닌지 확인 필요.
if (hRecvSock == INVALID_SOCKET)
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
continue;
else
ErrorHandling("aceept() error");
}
puts("Client connected...");
// accept()된 클라에게 WSAOVERLAPPED 구조체 할당할 준비
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; // CR 기반은 event 필요 없으므로, event 오브젝트에 다른 정보 넣어도 된다
WSARecv(hRecvSock, &(hbInfo->wsaBuf), 1, &recvBytes, &flagInfo, lpOvLp, ReadCompRoutine);
}
closesocket(hRecvSock);
closesocket(hLisnSock);
WSACleanup();
return 0;
}
// 이 함수가 호출됐다는 건, 데이터 입력이 완료됐다는 뜻
// WSARecv() 6번째 인자로 넘긴걸, 7번째 인자 함수에서 3번째 인자로 쓴다는게 고정
void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags)
{
LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent);
SOCKET hSock = hbInfo->hClntSock; // 아까 event 대신 넣어놨던 소켓 정보 활용
LPWSABUF bufInfo = &(hbInfo->wsaBuf);
DWORD sentBytes;
if(szRecvBytes==0)
{
closesocket(hSock);
free(lpOverlapped->hEvent);
free(lpOverlapped);
puts("Client disconnectd....");
}
else // echo!
{
bufInfo->len = szRecvBytes;
// 다 받았으면, 다음 alertable wait 때 WriteCompRoutine 실행
WSASend(hSock, bufInfo, 1, &sentBytes, 0, lpOverlapped, WriteCompRoutine);
}
}
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;
// 다 보냈으면, 다음 alertable wait 때 ReadCompRoutine 실행
WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine);
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
CR 기반 에코 서버
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = inet_addr(argv[1]);
servAdr.sin_port = htons(atoi(argv[2]));
if(connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr))==SOCKET_ERROR)
ErrorHandling("connect() error");
else
puts("Connected.....");
while(1)
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
break;
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);
}
CR 기반 에코 서버와 문제 없이 돌아가는 클라

위의 Overlapped IO 모델의 에코 서버가 지니는 단점
해결 방법
IO만 담당은 아니고, IO의 전후 과정을 전부 담당하는 쓰레드를 하나 이상 생성중요한 것