네트워크, 서버

WSAEventSelect model

춤추는수달 2022. 4. 29. 05:52

WSAEventSelect 모델이란

지난 번에 썼던 Select 모델과 같이 소켓 입출력 시 성공할 때까지 대기하거나 재시도하는 상황을 해결해주는 방법이다.

그러나 Select 모델과 달리 이벤트 객체를 생성해서 소켓을 관리해준다. 또한 Select모델과 달리 매 번 감시 소켓 목록을 초기화하는 등의 작업이 필요 없다. 또한 동기 방식이었던 Select 모델과 달리 비동기 방식의 모델이다.

여기서 말하는 이벤트란 커널에 존재하는 비트 플래그 같은 것이다. 커널에 bool 형 변수가 있고, 이를 껐다가 켰다가 (Signal, NonSignal 상태) 하면서 그 상태를 통지받는다고 생각하면 된다.

함수들

  • WSACreateEvent : 이벤트 생성
  • WSACloseEvent : 이벤트 삭제
  • WSAWaitForMultipleEvents : 신호 상태를 감지한다. 즉 소켓들이 동작을 실행할 준비가 될 때까지 대기하다가, 준비 됐음을 감지했을 때, 소켓 목록에서 준비된 소켓의 인덱스를 반환한다.
    • Time Limit를 매개변수로 넘겨주어 최대로 대기할 시간을 설정할 수 있다.
    • 매개변수로 받은 소켓 목록 중 단 한 개만 준비돼도 반환할 지, 아니면 목록의 모든 소켓이 준비돼야 반환할 지 여부도 매개변수로 넘겨서 설정할 수 있다.
  • WSAEnumNetworkEvents : 구체적인 네트워크 이벤트 알아내기. WSAWaitForMultipleEvents 함수로 이벤트가 Signaled 되었음을 감지하고 반환됐을 때, 해당 소켓이 어떤 작업을 하려고 했었는지(소켓에 연결된 이벤트가 어떤 속성을 가지고 있었는지) 확인하는 함수이다.
    • 선택적으로 소켓에 연결된 이벤트 객체를 매개변수로 넘겨주어 초기화(non Signaled 상태로 만들기) 시켜줄 수 있다. 
  • WSAEventSelect : 이벤트 와 소켓을 연동한다. 연동된 소켓은 자동으로 NonBlocking Socket이 된다. 연동할 때 어떤 동작이 준비됐을 때 이벤트가 Signaled될 것인지 이벤트의 속성을 설정해야 한다. 매개변수로 아래 옵션들을 설정해서 넘겨주면 된다.
    • FD_ACCETP : 접속한 클라이언트가 있음 -> accept
    • FD_READ : 데이터 수신 가능 -> recv
    • FD_WRITE : 데이터 송신 가능 -> send
    • FD_CLOSE : 상대가 접속 종료함
    • FD_CONNECT : 통신을 위한 연결 완료
    • FD_OOB : Out Of Band

방법

우선 기본적인 EventSelect 모델의 진행 방식을 설명하겠다.

  1. 소켓을 만든다.
  2. WSACreateEvent 함수로 이벤트 객체를 만든다.
  3. WSAEventSelect 함수로 1. 에서 만든 소켓과 2. 에서 만든 소켓을 연결한다. 이 때 소켓이 어떤 동작을 해야할 것인지 이벤트 속성을 설정한다.
  4. WSAWaitForMultipleEvents 를 호출하여 3.에서 연동한 이벤트 객체가 Signal될 때 까지 대기한다. 
  5. 4.에서 호출한 WSAWaitForMultipleEvents함수가 반환되면 WSAEnumNetworkEvents 함수를 호출하여 Signal된 이벤트 객체가 어떤 이벤트 속성을 가지고 있었는지 조사한다.
  6. 5. 에서 조사한 이벤트 속성에 따라 소켓의 동작을 실행한다.

이 것이 EventSlect 모델의 동작 방식이다. 그림으로 표현하면 아래와 같을 것이다.

이제 각 소켓마다 적절한 설정과 동작만 수행해주면 된다. 

예를 들어 Listen 작업을 하는 Listener 소켓은 위의 3. 과정에서 WSAEventSelect 함수의 3 번째 매개변수로 FD_ACCETP 를 넘겨주고, 6. 과정에서 accept 함수를 호출해주면 된다. 

::WSAEventSelect(listenSocket, listenEvent, FD_ACCEPT | FD_CLOSE)


사용 코드

	vector<WSAEVENT> wsaEvents;
	vector<Session> sessions;
	sessions.reserve(100);

	WSAEVENT listenEvent = ::WSACreateEvent();
	wsaEvents.push_back(listenEvent);
	sessions.push_back(Session{ listenSocket });
	if (::WSAEventSelect(listenSocket, listenEvent, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR)
		return 0;

	while (true)
	{
		int32 index = ::WSAWaitForMultipleEvents(wsaEvents.size(), &wsaEvents[0], FALSE, WSA_INFINITE, FALSE);
		if (index == WSA_WAIT_FAILED)
			continue;

		index -= WSA_WAIT_EVENT_0;

		//::WSAResetEvent(wsaEvents[index]);

		WSANETWORKEVENTS networkEvents;
		if (::WSAEnumNetworkEvents(sessions[index].socket, wsaEvents[index], &networkEvents) == SOCKET_ERROR)
			continue;

		// Listener 소켓 체크
		if (networkEvents.lNetworkEvents & FD_ACCEPT)
		{
			// Error-Check
			if (networkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
				continue;

			SOCKADDR_IN clientAddr;
			int32 addrLen = sizeof(clientAddr);

			SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
			if (clientSocket != INVALID_SOCKET)
			{
				cout << "Client Connected" << endl;

				WSAEVENT clientEvent = ::WSACreateEvent();
				wsaEvents.push_back(clientEvent);
				sessions.push_back(Session{ clientSocket });
				if (::WSAEventSelect(clientSocket, clientEvent, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)
					return 0;
			}
		}

		// Client Session 소켓 체크
		if (networkEvents.lNetworkEvents & FD_READ || networkEvents.lNetworkEvents & FD_WRITE)
		{
			// Error-Check
			if ((networkEvents.lNetworkEvents & FD_READ) && (networkEvents.iErrorCode[FD_READ_BIT] != 0))
				continue;
			// Error-Check
			if ((networkEvents.lNetworkEvents & FD_WRITE) && (networkEvents.iErrorCode[FD_WRITE_BIT] != 0))
				continue;

			Session& s = sessions[index];

			// Read
			if (s.recvBytes == 0)
			{
				int32 recvLen = ::recv(s.socket, s.recvBuffer, BUFSIZE, 0);
				if (recvLen == SOCKET_ERROR && ::WSAGetLastError() != WSAEWOULDBLOCK)
				{
					// TODO : Remove Session
					continue;
				}

				s.recvBytes = recvLen;
				cout << "Recv Data = " << recvLen << endl;
			}

			// Write
			if (s.recvBytes > s.sendBytes)
			{
				int32 sendLen = ::send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
				if (sendLen == SOCKET_ERROR && ::WSAGetLastError() != WSAEWOULDBLOCK)
				{
					// TODO : Remove Session
					continue;
				}

				s.sendBytes += sendLen;
				if (s.recvBytes == s.sendBytes)
				{
					s.recvBytes = 0;
					s.sendBytes = 0;
				}

				cout << "Send Data = " << sendLen << endl;
			}
		}

		// FD_CLOSE 처리
		if (networkEvents.lNetworkEvents & FD_CLOSE)
		{
			// TODO : Remove Socket
		}
	}

 

 


주의사항

  • 소켓 1개당 이벤트 1개를 만들어야함.
  • 소켓은 자동 넌블로킹모드로 바뀜.
  • accept 함수를 통해 반환된 소켓은 listenSocket과 같은 속성을 가짐. 즉 accept 함수로 반환된 clientSocket은 다시 FD_READ 혹은 FD_WRITE 등의 이벤트를 다시 등록해야한다.
  • 이벤트 발생 시 그에 따른 적절한 조치를 취해주기 전 까지는 다시 이벤트가 발생하지 않음. 즉 read 이벤트가 발생하면 해당 이벤트와 연결된 소켓으로 read 작업을 하기 전 까지 다시 그 이벤트가 발생할 일은 없음.
  • Select 모델과 마찬가지로 WSAWaitForMultipleEvents 함수 호출 한 번에 최대 64개의 이벤트와 소켓을 감시할 수 있음.

'네트워크, 서버' 카테고리의 다른 글

Completion Port 모델  (0) 2022.04.30
Overlapped 모델(콜백함수)  (0) 2022.04.30
Select model  (0) 2022.04.28
Socket Options  (0) 2022.04.28
NAT(Network Address Translation)  (0) 2022.02.21