반응형

// 블록킹 모드 //
블록킹 소켓은 모든 윈속 API의 호출이 얼마간의 시간동안 블록되도록 한다.

SOCKET sock;
char buff[256];
int done = 0, nBytes;
....
while(!done)
{
     nByte = recv(sock, buff, 65);
     if(nBytes == SOCKET_ERROR)
     {
    printf("recv failed with error %d\n", WSAGetLastError());
    return;
     }
     DoComputationOnData(buff);
}
---> 문제점??


데이터가 없다면 recv 함수는 영원히 리턴되지 않는다(타임아웃을 설정한 경우 제외)

~> 블록킹 모드가 모든 경우에 단점만 있는 것은 아니다. 때에 따라서는 블록킹 모드가 유용한 경우가 있다.
우리가 일반적으로 사용하는 대화형 프로토콜들이 그러한데, 예를 들자면 HTTP, TELNET, FTP, SMTP 등이 있다.
이러한 프로토콜은 모두 요청->응답의 기본 구조로 되어있고 정해진 순서에 의해 데이터를 주고 받는다.

ex) HTTP
1. 연결
2. 전송(요청) "GET index.html\r\n\r\n"
3. 수신(응답) "HTTP/1.0 200 OK\r\n\r\n<html><body></body></html>"
4. 종료
이런 경우에 클라이언트는 send로 요청을 모두 전송하고 나서 recv로 서버의 응답을 수신해야 한다.

// 블록킹 모델 //
대부분의 프로그래머들이 처음에는 블록킹 모델을 이용하여 소켓을 개발한다. 블록킹 모델은 이해하기 쉽고 만들기 쉽기 때문이다.
블록킹 모델에서는 하나 이상의 스레드를 사용할 필요가 있다. 때에 따라서는 send나 recv에 대하여 하나의 스레드를 할당할 필요가 있다.
블록킹 모델의 장점은 단순하다는데 있다. 그래서 단순한 소켓 응용 프로그램을 개발하기에 적합하다.
블로킹 모델의 단점은 각 연결마다 스레드가 필요하므로 시스템 리소스의 낭비가 심해진다.

///////////////////////////////// Select /////////////////////////////////
이 모델은 UNIX 기반의 버클리 소켓에서 사용되던 모델이다. select 모델은 윈속 1.1 이상부터 가능하며 블록킹 없이 하나 이상의 소켓들의 I/O를 다루기 위하여 사용된다.  버클리 소켓과 호환되므로 select를 사용하는 버클리 소켓 응용프로그램은 큰 수정 없이 윈속에서 작동된다.

select 함수는 소켓의 데이터가 존재하거나 소켓에 데이터를 쓸 수 있는지 판단하는 데 이용한다.
select 함수는 소켓이 특정 조건에 다다를 때까지 블록된다.
-함수 원형-
int select(
     int nfds    // 버클리 소켓과의 호환성을 위해 있는 것으로 무시된다.
     fd_set *readfds,   // 소켓이 읽기 가능한 상태인지 체크한다.
     fd_set *writefds,   // 소켓이 쓰기 가능한 상태인지 체크한다.
     fd_set *exceptfds,   // 대역외 데이터의 존재 여부를 체크한다.
     const struct timeval * timeout
};
readfds는 다음과 같은 경우를 체크하는데 사용
- 수신할 데이터가 있는가?
- 연결이 끊어졌는가? (close, reset, terminate)
- listen 호출 후에 새로운 연결이 대기중이어서 accept가 성공할 것인가?

writefds
- 데이터가 송신되었는가?
- 연결이 성공되었는가? (넌블록킹 모등서 연결을 시도했다면)

exceptfds
- 연결이 실패하였는가?
- 수신할 OOB 데이터가 있는가?

~~> 어떤 소켓에 대하여 수신할 데이터가 있는지 알아보기 위해서는 readfds에 소켓을 추가하고 select 함수가 리턴될 때까지 기다리면 된다.
select 함수가 리턴되면 소켓이 readfds에 존재하는지 확인하여 존재한다면 수신할 데이터가 도착됐다고 판단.

select 함수가 성공적으로 수행되면, 입력한 fd_sets 구조체의 소켓 중에 I/O가 발생한 소켓의 총 합이 리턴된다.(I/O가 활성화된 소켓들의 합)

select 함수를 사용하기 위해서는 먼저 소켓 핸들을 fd_set에 지정해야 한다.
fd_set을 이용하려 소켓 다루기
- FD_ZERO(*set): set을 비운다. set은 사용전에 비워져야 한다.
- FD_CLR(s, *set): 소켓 s를 set에서 지운다
- FD_ISSET(s, *set): set에 소켓 s가 포함되었는지 확인한다. 포함되었으면 리턴값은 TRUE이다
- FD_SET(s., *set): set에 소켓 s를 추가한다.

// 하나 이상의 소켓 핸들에 대해서 select를 호출하는 과정 //
1. FD_ZERO를 이용하여 사용할 fd_set을 초기화한다.
2. FD_SET을 이용하여 사용할 소켓을 fd_set에 추가한다.
3. select 함수를 호출하여 소켓의 I/O가 활성화될 때까지 기다린다. select 함수가 리턴되며 fd_set은 업데이트 된다.
4. FD_ISSET을 이용하여 어떤 소켓의 I/O가 활성화되었는지를 확인한다.
5. I/O가 활성화된 소켓에 대하여 I/O 작업을 수행하고 끝나면 다시 1의 과정으로 되돌아간다. 

select 함수가 리턴되면 fd_set에서 I/O 작업이 없는 소켓들은 제거된다. --> FD_ISSET을 이용하면 활성화된 I/O 작업이 있는 소켓을 확인할 수 있다.

// 하나의 소켓에 대하여 select를 수행하는 방식 //
SOCKET s;
fd_set fdread;
int ret;

// 소켓을 생성하여 연결을 받아들인다.

// 소켓의 I/O를 관리하는 루틴
while(TRUE)
{
 // select()를 호출하기 전에 항상 fdread를 제거한다.
 FD_ZERO(&fdread);

 // 소켓 s를 fdread에 추가한다
 FD_SET(s, &fdread);

 if((ret = select(0, &fdread, NULL, NULL, NULL)) == SOCKET_ERROR)
  {
  // 에러 처리
 }
 if(ret > 0)
 {
 // 이 경우는 select() 가 1을 리턴한 경우다, 하나이상의 소켓을 사용할 때는 1보다 큰
 // 값이 리턴될 수 있다. 이런 경우에는 응용 프로그램에서 어떤 소켓의 값이 설정되었는지
 // 체크해야 한다.
 }
}

select의 장점은 여러 소켓이나 여러가지 I/O에 대하여 하나의 스레드를 이용하여 처리할 수 있다는 것이다.
따라서 연결이 많아질수록 스레드가 무한정 증가되는 것을 방지할 수 있다.
select의 단점은 fd_set에 설정할 수 있는 소켓의 개수가 제한되어 있다는 것이다.(default: 64개)

//////////////////////////////////////////// WSAEventSelect ////////////////////////////////////////////
WSAEventSelect는 이벤트 오브젝트(Event Object)를 이용하여 소켓의 이벤트를 통지받을 기능을 제공한다.
WSAAsyncSelect와의 차이점은 윈도우 메시지가 아닌 이벤트 오브젝트를 통하여 소켓의 이벤트를 전달 받는다.

// 소켓의 이벤트를 이벤트 오브젝트로 통지 받는 방법 //
이벤트로 통지 받기 위해서는 이벤트 오브젝트를 생성해야 한다.
WSAEVENT WSACreateEvent(void)
Non-Signaled, Manual-Reset모드로 이벤트 커널 오브젝트 생성됨

int WSAEventSelect(
SOCKET s,   // 이벤트를 통보받기를 원하는 소켓
WSAEVENT hEventObject,  // 이벤트를 전달받을 이벤트 오브젝트
long lNetworkEvents  // 전달받을 소켓 이벤트의 종류
);


소켓의 이벤트를 이벤트 오브젝트로 받고자 한다면 이벤트 오브젝트의 상태가 시그널 상태로 변화될 때까지 기다리는 I/O 프로세싱이 필요하다.
WSAWaitForMultipleEvents함수는 하나 이상의 이베트 오브젝트의 상태가 시그널 상태로 변화될 때까지 프로세싱을 대기시키는 기능을 제공한다.
타임아웃을 지정할 수 있으면 리턴 값으로는 시그널 상태로 변한 이벤트의 번호를 알려준다.
-함수원형-
DWORD WSAWaitForMultipleEvents(
DWORD cEvent,    // 감시할 이벤트의 개수
const WSAEVENT FAR * lphEvents,  // 이벤트의 배열 -> 수용할 수 있는 이벤트의 개수가 WSA_MAXIMUM_WAIT_EVENTS(64로 정의되어 있음)
BOOL fWaitAll,    // TRUE: 모든 이벤트 오브젝트들이 시그널 상태가 될때까지 기다린다. FALSE: 하나의 시그널 상태로도 리턴된다.
DWORD dwTimeout,   // WSA_INFINITE이면 오브젝트가 시그널 상태가 될 때까지 계속 기다린다.
BOOL fAlertable    // Completion routine에서 사용됨.
);

WSAWaitForMultipleEvents 함수가 리턴되면 리턴값은 시그널 상태로 변화된 이벤트 오브젝트의 배열의 인덱스를 리턴한다.
따라서 이 인덱스를 이용하여 어떤 소켓에서 이벤트가 발생되었는지 확인할 수 있다.
리턴값을 배열의 인덱스로 사용할 때 다음과 같이 리턴 값에서 WSA_WAIT_EVENT_0을 빼야 한다..(?)
Index = WSAWaitForMultipleEvents(....);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];


WSAEnumNetworkEvents는 발생된 이벤트에서 소켓 이벤트의 종류를 알아내는 함수이다.
-함수원형-
int WSAEnumNetworkEvents(
SOCKET s,    // 이벤트가 발생된 소켓의 핸들
WSAEVENT hEventObject,   // 소켓과 연결된 이벤트 오브젝트의 핸들
LPWSANETWORKEVENTS lpNetworkEvents // 발생된 이벤트의 종류와 에러 코드를 포함하고 있다.
);

typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENT];  // 이벤트에 해당하는 에러 코드의 배열이다. 각 소켓 이벤트에 대하여 유사한 이름의 에러 코드 인덱스가 존재
     // 이 에러 코드의 이름은 소켓 이름의 뒤에 "_BIT"가 붙는다.
}WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

// 에러 코드 검출 방법 //
// FD_READ를 통지 받았다
if(NetworkEvents.lNetworkEvents & FD_READ)
{
 if(NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
 {
  printf("FD_READ failed with error %d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
 }
}

//////////////////////////////////////////// WSAAsyncSelect ////////////////////////////////////////////
윈속의 소켓 이벤트를 윈도우 메시지를 통하여 비동기적으로 통보 받을 수 있다.
소켓을 생성한 후 WSAAsyncSelect를 호출하면 이러한 기능을 사용할 수 있다.

WSAAsyncSelect 모델을 이용하기 위해서는 우선 CreateWindow를 이용하여 윈도우를 생성하여 윈도우 프로시저가 동작되도록 해야 한다.
윈도우를 생성한 후 소켓을 생성하고 WSAAsyncSelect를 호출하여 메시지를 통보받으면 된다.

WSAAsyncSelect는 윈도우즈에서 제공하는 소켓 I/O를 컨트롤 하는 함수이다
WSAAsyncSelect는 소켓을 감시하고 있다고 원하는 이벤트가 소켓에 발생하면 사용자가 미리 정의해둔 메시지를 이용하여 그 이벤트의 발생을 사용자에게 알려준다.
이벤트 발생 메시지가 발생하면 거기에 맞는 가상 함수를 실행시켜준다.
-함수원형-
int WSAAsyncSelect(
SOCKET s,   // 통지를 받기 원하는 소켓
HWND hWnd,   // 이벤트가 발생했을 때, 그 이벤트를 통보 받을 윈도우의 핸들.
unsigned int wMsg,  // 이벤트가 발생했을 때, 사용자에게 통보될 사용자 정의 메시지
long lEvent   // WSAAsyncSelect에서 감지할 이벤트의 종류가 들어간다.
);

wMsg는 다른 윈도우 프로시저용 메시지와 섞이지 않도록 WM_USER보다 큰 값을 사용한다.

WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE);
----> 소켓 s가 서버에 연결하거나, 데이터를 수신할 때가 되거나, 데이터가 전송되거나, 연결이 끊어지면
hwnd에 이벤트의 발생을 WM_SOCKET메시지로 통보하여라

이벤트의 종류에는 accept 요청 시에 발생하는 FD_ACCEPT,
connect 요청 시 발생하는 FD_CONNECT
읽을 데이터가 있을 때 발생하는 FD_RECV,
데이터를 보낼 수 있을 때 발생하는 FD_WRITE,
소켓을 닫을 때 발생하는 FD_CLOSE

//////////////////////////////////////////////////
종류  기능
FD_ACCEPT 연결 요청이 들어왔을 때 통지 받음
FD_CONNECT 연결이 완료되거나 멀티캐스트 가입이 완료되었을 때 통지 받은
FD_READ  수신할 데이터가 있을 때 통지 받음
FD_WRITE 전송이 완료 되었을 때 통지 받음
FD_CLOSE 소켓이 종료 되었을 때 통지 받음
FD_OOB   OOB 데이터가 도착했을 때 통지 받음
//////////////////////////////////////////////////

WSAAsyncSelect는 다음과 같은 순서로 이용한다
1. WSAAsyncSelect에서 사용할 사용자 정의 메시지를 만든다.
2. WSAAsyncSelect에 사용할 소켓을 등록한다.
3. 이벤트에 따라서 작업을 처리한다.

// 윈도우 프로시저 //
LRESULT CALLBACK WindowProc(
 HWND hWnd,  // 윈도우 프로시저를 호출한 윈도우 핸들
 UINT uMsg,  // 처리해야 할 메시지
 WPARAM wParam,  // 이벤트가 발생된 소켓의 핸들
 LPARAM lParam  // low: 소켓에서 발생된 이벤트의 종류, high: 소켓에서 발생된 에러
);
// 메크로 제공 //
WSAGETSELECTERROR: high 에러
WSAGETSELECTEVENT: low 이벤트
&&&&&&&&&&&&&&&&&&&&&&&&
WSAAsyncSelect 작성하기
&&&&&&&&&&&&&&&&&&&&&&&&

[출처] 입출력방식|작성자 에버어어

반응형

+ Recent posts