I/O (입출력)의 중첩
I/O의 중첩이라는 것은 쓰레드의 관점에서 동시에 둘 이상의 데이터 전송을 중첩 시키는 것
데이터 전송을 중첩시키기 위해서는 데이터의 입출력 함수가 Non-Blocking 모드로 동작
데이터 전송의 대상이 소켓 BCD로 국분-> IO 중첩은 하나의 소켓을 대상으로 진행
Overlapped I/O - Overlapped IO가 아니더라도 IO를 중첩시킬 수 있다. - Overlapped IO의 포커스는 IO 가 아닌, 입출력의 완료 확인방법 에 있다. - select가 아닌 정확한 비동기는 I/O에 대한 명령을 커널로 전송 후 끝났을 떄 signal을 자동으로 받아 특정 동작을 수행 하는 것 -> 이를 overpaaped I/O를 사용
user 와 kernel 사이에서 계속해서 확인
Overlapped I/O의 소켓 생성
WSASocket 함수의 마지막 전달인자로 WSA_FLAG_OVERLAPPED 가 전달되어야 한다.
WSASocket(PF_INET.SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
Overlapped I/O를 진행하는 WSASend 함수
IpOverlapped 와 IpCompletionRoutine 는 가장 관심을 둬야 하 두개의 매개변수
IpCompletionRoutine 는 저장해서 필요시 호출
WSAEVENT event;
WSAOVERRLAPPED overlapped;
WSABUF dataBUF;
char buf[BUF_SIZE] = {"전송할 데이터"};
int recvBytes = 0;
....
event=WSACreateEvent(); //이벤트 객체 생성
memset(&overlapped, 0 , sizeof(overlapped)); // 모든 비트 0으로 초기화!
overlapped.hEvent = event;
dataBuf.len =sizeof(buf);
dataBuf.buf=buf;
WSASend(hSocket, &dataBuf, 1, &recvBytes,0,&overlapped,NULL);
함수의 호출과 동시에 데이터 전송이 완료되지 못하면 의미를 갖니 않는다.
overlapped는 이벤트 발생 정보를 담은 구조체
Event 오브젝트 기반 I/O확인의 중요한 두 가지 1. IO가 완료된면 WSAOVERLAPPED 구조체 변수가 참조하는 Event 오브젝트 signaled 상태가 됨. 2. IO의 완료 및 결과를 확인하려면 WSAGetOverlappedResult 함수를 호출
evObj = WSACrerateEvent(); //이벤트 객체 생성
memset(&overlapped, 0 , sizeof(overlapped)); //초기화
//설정
overlapped.hEvent = evobj;
dataBuf.len =strlen(meg)+1;
dataBuf.buf = msg;
//시그널 상태를 확인한 다음에 실제 전송된 바이트 크기를 확인
if(WSASend(hSocket, &dataBuf, 1, &sendBytes, 0, &overlapped, NULL) == SOCKET_ERRORR)
{
if(WSAGetLastError() == WSA_IO_PEDDING) // 정확한 오류 판별
{
puts("Background data send");
WSAWaitForMUltipleEvents(1, &evobj, TRUE, WSA_INFINITE, FALSE); //이벤트 발생 시 evobj가 signaled
WSAGetOverlappedResult(hsocket, &overlapped, &sendBytes, FLASE, NULL);
Overlapped I/O를 진행하는 WSA Recv 함수
WSASend 함수와 호출의 방식이 매우 유사
Evnet 오브젝트 기반의 Recv - WSAGetOverlappedResult 함수는 전송된 바이트 수의 확인, 수신된 바이트 수도 확인
ebobj = WSACreateEvent(); // 이벤트 객체 생성
memset(&overlapped, 0, sizeof(overlapped));
overlapped.hEvent = evobj;
dataBuf.len=BUF_SIZE;
dataBuf.buf=buf;
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!");
}
}
데이터 송수신 결과의 확인 방법
데이터 전송이 계속 진행되는 상황에서는 WSASend 함수가 SOCKET_ERROR를 반환 하고, WSAGETLastError 함수호출을 통해서 확인 가능한 오류코드로는 WSA_IO_PENDING이 등록 된다. 그리고 이 경우에는 위의 함수호출을 통해서 실제 전송된 데이터의 크기를 확인
WSA_IO_PENDING을 통해 send() 함수에 오류인지, 아직 Data를 단순히 아직 덜 받아온 것인지를 판단
fWait를 예를 들어 1 2 3 4 에서 1 2 배열이 성공 시 빠져 나오고 3 4 가 FALSE여도 바로 FALSE를 반환하여 나온다.
WSAGetLastError
오류 발생시 해당 오류 정보를 위의 함수호출을 통해서 확인
위의 함수는 오류의 발생으로 인해서 등록된 오류의 상태 값을 반환
Completion Routine
Completion Routine의 이해와 등록 - 등록과 호출로 이루어짐 - IO 가 완료되었을때 호출되는 함수를 가리켜 Completion Routine이라 한다. - IO 가 완료되면, 미리 등록된 CompletionRoutine이 운영체제에 의해서 자동으로 호출 - CompletionRoutine 이 호출되지 위해서는 해당 쓰레드가 alert wati상태에 놓여야 한다. - Alert wait은 운영체제가 전달하는 메시지의 수신이 가능한 상태를 말한다.
Alertable wait 상태로 진입에 사용되는 함수들 - WaitForSingleObjectEX - WaitForMultipleObjectsEx - WSAWaitForrMultipleEvents - SleepEX
Completion Routine 기반의 IO 완료확인은 함수의 등록과 등록된 함수의 호출을 통해서 이뤄진다. 단, 등록된 함수가 호출될 수 있도록 쓰레드는 Alert wait상태가 되어야 한다.
Completion Routine 의 예
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#define BUF_SIZE 1024
void CALLBACK CompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);
void ErrorHandling(char *message);
WSABUF dataBuf;
char buf[BUF_SIZE];
int recvBytes=0;
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hLisnSock, hRecvSock;
SOCKADDR_IN lisnAdr, recvAdr;
WSAOVERLAPPED overlapped;
WSAEVENT evObj;
int idx, recvAdrSz, flags=0;
if(argc!=2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hLisnSock=WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
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);
hRecvSock=accept(hLisnSock, (SOCKADDR*)&recvAdr,&recvAdrSz);
if(hRecvSock==INVALID_SOCKET)
ErrorHandling("accept() error");
memset(&overlapped, 0, sizeof(overlapped));
dataBuf.len=BUF_SIZE;
dataBuf.buf=buf;
evObj=WSACreateEvent(); //Dummy event object (이벤트 객체 생성)
// CompRoutine -> Completion Routine 등록
// &overlapped -> Overlapped IO를 위한 인자의 전달
if(WSARecv(hRecvSock, &dataBuf, 1, &recvBytes, &flags, &overlapped, CompRoutine)
==SOCKET_ERROR)
{ // 오류확인
if(WSAGetLastError()==WSA_IO_PENDING)
puts("Background data receive");
}
//마지막 매개변수 TRUE -> Alert Wait 상태로 진입!
idx=WSAWaitForMultipleEvents(1, &evObj, FALSE, WSA_INFINITE, TRUE);
if(idx==WAIT_IO_COMPLETION)
puts("Overlapped I/O Completed");
else // If error occurred!
ErrorHandling("WSARecv() error");
WSACloseEvent(evObj);
closesocket(hRecvSock);
closesocket(hLisnSock);
WSACleanup();
return 0;
}
//운영체제가 사용자에게 알리는 Callback 함수
//Callback 함수를 통해 recv 호출이 완료되면 호출되도록 구현
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);
}
}
void ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}