I/O (입출력)의 중첩
- I/O의 중첩이라는 것은 쓰레드의 관점에서 동시에 둘 이상의 데이터 전송을 중첩시키는 것
- 데이터 전송을 중첩시키기 위해서는 데이터의 입출력 함수가 Non-Blocking 모드로 동작
- 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);
}