전에 이미 공부한 내용이긴 하지만
Overlapped IO Model 보다 앞서서 봐야할 내용이므로 정리 차원에서 포스팅.
Select함수를 사용하는 다중접속 서버에 구현 방법에 대해서 먼저 알아야 하지만
이것도 나중에(..) 정리하고 우선 Select 함수에 대한 기본적인 이해가 있다는 전제.
동기 vs 비동기
'동기'적 처리에 대한 단점 : recv 함수를 사용할 때(물론 select 함수를 사용하지 않은 상황), 서버는 받은 데이터가 존재할 때 까지 recv 함수의 반환을 기다려야 함. 반환을 기다리는 동안 다른 연산을 처리한다던가 하는 유용한 일을 처리할 수도 있는데 말이다.
이러한 단점을 개선해서
'비동기'적 처리로 서버를 구현할 수가 있다.
Select 함수로도 물론 처리할 수 있다. 다만, 개발자가 for문을 돌면서 소켓에 뭔가 변화가 생겼는지 확인해주어야 하고 뭐 여튼 구현의 복잡도가 올라가는 듯.
그래서 쓰이는 것이 이벤트 방식. WSAEventSelect / WSACreateEvent / WSAWaitForMultipleEvents / WSACloseEvent / WSAEnumNetworkEvents ...
등의 함수를 사용한다.
솔직히 내용보다는 구현에 좀 더 무게를 둠.
초기단계
1. Client 받는 용으로 쓰이는 소켓 64개 / WSAEVENT 타입의 이벤트 오브젝트 64개
새로운 이벤트를 받아올 WSAEVENT 타입의 변수. 어떤 종류의 이벤트가 발생했는지 받아올 WSANETWORKEVENTS 타입의 변수.
정도가 필요하다. 선언해주자.
2. 소켓 생성 및 주소할당 하는 과정은 똑같다.
다만, 그 이후의 과정이 아래와 같다.
WSAEVENT m_newEvent = WSACreateEvent();
if(WSAEventSelect(m_hMySocket, m_newEvent, FD_ACCEPT) == SOCKET_ERROR)
return SOCKET_ERROR;
if(listen(m_hMySocket,64) == SOCKET_ERROR)
return SOCKET_ERROR;
** 이 방식의 안타까운 문제점 중 하나. 최대 핸들의 수가 64개이다. 이를 확장하려면 쓰레드 생성을 통해서 하던지, 핸들을 저장하고 있는 배열을 구분해서 WSAWaitForMultipleEvents 함수를 구분한 만큼 호출하던지 해서 구현해야 함.
그래서 이 방식으로 라이브러리를 만들다가 안되겠어서 다시 공부를 시작함-_ㅠ
업데이트문을 돌돌. 어차피 나만 볼테니 코드 복붙.
변수 선언부
enum SERVER_EVENT
{
EVENT_NONE = -1,
EVENT_CONNECT = 0,
EVENT_REQ,
EVENT_DISCONNECT,
};
struct EVENT_RESULT
{
SERVER_EVENT EventFlag;
int Index;
};
SOCKET m_hMySocket;
WSADATA m_wsaData;
SOCKADDR_IN m_ServAddr;
SOCKET m_hSockArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT m_hEventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT m_newEvent;
WSANETWORKEVENTS m_netEvents;
int m_iSocketTotal;
EVENT_RESULT m_CurEventResult;
EVENT_RESULT 를 따로 선언한 것은 Update를 돌 서버 메인.cpp에서 어떤 소켓에 어떤 이벤트가 발생했는지 알아야 하기 때문이다. 이벤트가 발생하면, 어떤 소켓에 어떤 이벤트가 발생했는지 구분해서 struct를 세팅한 후, 이를 리턴하여 서버 프로젝트에서 사용할 수 있도록 함.
업데이트 돌돌
EVENT_RESULT NerworkInterface :: RunEvent(SOCKET &pSocket)
{
int index = WSAWaitForMultipleEvents(m_iSocketTotal, m_hEventArray, FALSE, WSA_INFINITE, FALSE);
index = index-WSA_WAIT_EVENT_0;
m_CurEventResult.EventFlag = EVENT_NONE;
m_CurEventResult.Index = -1;
// m_hSockArray 랑 m_hEventArray 는 항상 붙어다녀야 함.
WSAEnumNetworkEvents(m_hSockArray[index], m_hEventArray[index], &m_netEvents);
if(m_netEvents.lNetworkEvents & FD_ACCEPT)
{
SOCKADDR_IN clntAddr;
int clntLen = sizeof(clntAddr);
SOCKET hClntSock = accept(m_hSockArray[index], (SOCKADDR*)&clntAddr, &clntLen);
m_newEvent = WSACreateEvent();
WSAEventSelect(hClntSock, m_newEvent, FD_READ|FD_CLOSE);
m_hEventArray[m_iSocketTotal] = m_newEvent;
m_hSockArray[m_iSocketTotal] = hClntSock;
pSocket = m_hSockArray[m_iSocketTotal];
m_iSocketTotal++;
m_CurEventResult.EventFlag = EVENT_CONNECT;
m_CurEventResult.Index = m_iSocketTotal-1;
}
else if(m_netEvents.lNetworkEvents & FD_READ)
{
pSocket = m_hSockArray[index];
m_CurEventResult.EventFlag = EVENT_REQ;
m_CurEventResult.Index = index;
}
else if(m_netEvents.lNetworkEvents & FD_CLOSE)
{
WSACloseEvent(m_hEventArray[index]);
closesocket(m_hSockArray[index]);
m_iSocketTotal--;
SortSockets(index);
SortEvents(index);
m_CurEventResult.EventFlag = EVENT_DISCONNECT;
m_CurEventResult.Index = index;
}
return m_CurEventResult;
}
1. WSAWaitForMultipleEvents 이벤트가 현재 발생 했는지 안했는지 체크하여 이벤트 발생한 Index를 반환.
1) m_iSocketTotal : 몇개의 이벤트 어레이 중에서 받아와야 하는지
2) m_hEventArray : 이벤트 오브젝트 어레이.
3) FALSE : 전체 핸들이 Signaled 상태가 되어야 리턴할건지 아니면 하나만 Signaled 되도 리턴할건지. 전자이면 TRUE, 후자이면 FALSE.
4) WSA_INFINITE : 인자 얼마나 기다릴건가. 하나라도 Signal되길 기다릴거면 WSA_INFINITE. 아니면 Timeout을 지정하자.
5) 다섯번재 인자는 아직 잘 모르겠어.
6) 리턴 값에서 WSA_WAIT_EVENT_0 을 빼면, 두번째 매개변수, 즉 m_hEventArray를 기준으로 Signaled 상태인 핸들의 Index가 구해진다. 한가지 의문은 운영체제마다 다른건지 어쩐건지 모르겠는데 내 컴퓨터에서 WSA_WAIT_EVENT_0는 0이었던 것 같다. 그래도 뭐 혹시 모르니 WSA_WAIT_EVENT_0을 빼주는게 좋다. 근데 한번에 여러곳에서 signal 상태가 된다면 가장 작은 Index부터 반환된다. 반환된 핸들은 다시 이벤트가 발생할 때 까지 Non-Signal 상태가 되니까 다음번 WSAWaitForMultipleEvents 함수 호출에 반환된다. 그러니 걱정ㄴㄴ해. 타임아웃은 타임아웃이라 ㄾ
2. WSAEnumNetworkEvents
이벤트 유형 정보를 가져와보자. 첫째 둘째 인자로 각각 이벤트가 발생한 소켓과 이벤트 핸들을 넘겨주면 됨. 세번째 인자로 이제 m_netEvents 를 넣어주면 m_netEvents 여기에 어떤 이벤트가 발생했는지, 혹시 실패했다면 어떤 에러가 있었는지 담겨서 나온다.
3. if(m_netEvents.lNetworkEvents & FD_ACCEPT)
어떤 이벤트가 발생했는지 lNetworkEvents 이 녀석이 알고있다. 비트연산으로 조져버령
클라가 접속하면 FD_ACCEPT 과 비트연산한게 참이 될것이고 뭔가 클라가 보낸게 있다면 FD_READ과 비트연산한게 참. 접속종료는 FD_CLOSE.
뭐... 더 설명할 게 있겠나.
아 피고냉...
막상 설명하려니까 한도 끝도 없다; 책에도 고생스러운 부분이라고 적혀있어;; 하지만 나같은 멍청이도 대충 쓸 줄 알게 되었기 때문에 그냥 다음으로 넘어간다.
이번주중으로 Overlapped IO모델/IOCP모델 봐야지'ㅅ'
'스터디 > Server' 카테고리의 다른 글
AcceptEX 개객기야 (0) | 2016.07.05 |
---|---|
일단 IOCP 채팅서버 만드는 건 끝! (0) | 2016.06.28 |
Overlapped IO 모델 (0) | 2016.05.27 |
서버 라이브러리 구조 (0) | 2016.04.13 |
주소 할당 (0) | 2016.01.28 |