IOCP 게임서버

IOCP 실습 7 Echo 서버 - 비동기 Accept 함수 사용하기

묭묭.cpp 2023. 9. 1. 00:54

이번 실습 과제는 비동기 Accept 함수를 사용하는 것이다.

이전까지는 접속을 받을 때 동기 함수로 accept를 받아주었는데 이것을 비동기 방식으로 바꾸면 모든 네트워크 I/O 작업이 비동기 방식으로 바뀐다.

로직의 흐름만 이해하고 내 마음대로 프로그램을 코딩했더니 많은 오류가 나타나기 시작했다.

지금부터 그 여정을 기록해보도록 하겠다.

 

#출처 https://www.youtube.com/watch?v=dQdY6f7rnGs&t=323s 

 

1. 비동기 Accept 함수

먼저 비동기 Accept 함수를 알아보고 시작하겠다.

일단 AcceptEx 함수를 사용하면 되는데 이 함수를 사용하려면 라이브러리 추가와 헤더파일 include가 필요하다.

#pragma comment(lib, "mswsock.lib")
#include <MSWSock.h>

해당 라이브러리에 MSWSock.h 헤더파일에 AcceptEx 함수가 정의되어있다.

AcceptEx 함수는 다음과 같이 사용하면 된다.

BOOL AcceptEx(
	SOCKET sListenSocket,			// 리슨 소켓
	SOCKET sAcceptSocket,			// 접속을 받을 소켓
	PVOID lpOutPutBuffer,			// 로컬 주소와 리모트 주소를 받기 위한 버퍼, NULL이 될 수 없음
	DWORD dwReceiveDataLength,		// 접속 후 전송되는 최초 데이터를 수신하기 위한 버퍼의 크기, 필요 없으면 0
	DWORD LocalAddressLength,		// 소켓 주소 구조체 길이 + 16
	DWORD dwRemoteAddressLength,	// 소켓 주소 구조체 길이 + 16
	LPWORD lpdwBytesReceived,		// lpOverlapped를 사용하면 무시, NULL 입력
	LPOVERLAPPED lpOverlapped		// OVERLAPPED 구조체의 포인터
);

 

그럼 이제 어떻게 설계하면 될지 생각해보자.

2. 설계와 구현

이전까지는 설계와 구현을 나누어 글을 작성하였는데 다시 읽어보니 대부분이 겹치는 내용인 것 같아 이번 글에서는 합쳐서 작성해보려고 한다.

 

일단 추가로 Client 클래스에 필요한 변수를 선언하자.

INT32					mIsConnected = 0;
char					mAcceptBuf[64];
UINT32					mLastClosedTimeSec = 0;
stOverlappedEx				m_stAcceptOverlappedEx;
  • mIsConnected : 현재 클라이언트가 접속을 요청했는지 확인하는 변수
  • mAcceptBuf : AcceptEx 함수의 3번째 인자로 넘겨줄 버퍼
  • mLastClosedTimeSec : 마지막으로 연결이 종료된 시간
  • m_stAcceptOverlappedEx : Accept를 요청하고 IOCP 오브젝트에서 완료를 확인하기 위한 구조체

여기서 마지막으로 연결이 종료된 시간은 연결이 끊긴지 얼마 안된 클라이언트에 바로 연결을 시도하면 문제가 발생할 수 있으므로 기록하여 시간을 비교한 뒤 다음 연결을 받아주기 위함이다.

예를들어 아직 소켓의 종료가 완전히 끊기지 않았다던가 하는 상황이 있을 수 있다.

 

이제 연결을 요청하는 과정을 살펴보자.

연결을 요청하는 함수는 Listen 소켓을 인자로 받는다.

  1. isConnected 변수를 1로 설정한다. (연결 중이라는 의미)
  2. 접속을 받을 소켓을 생성한다 : WSASocket
  3. stOverlappedEx 구조체에 값을 채워넣는다.
  4. AcceptEx 함수로 연결 요청을 예약한다.

위 함수를 구현하는 것은 크게 어렵지 않았다. 단순히 새로운 함수인 AcceptEx를 사용하면 되는 것이기 때문이다.

그럼 이제 IOCPServer 클래스의 Accept 쓰레드에서 루프를 돌며 접속 요청을 예약해주면 된다.

void AccepterThread()
	{
		while (mIsAccepterRun)
		{
			auto nowTimeSec = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
			for (auto client : mClientInfos)
			{
				if (client->IsConnected())
					continue;

				if (nowTimeSec < client->getLastClosedTime())
					continue;

				auto diff = nowTimeSec - client->getLastClosedTime();
				if (diff <= RE_USE_SESSION_WAIT_TIMESEC)
					continue;

				client->PostAccept(mListenSocket);
			}
		}
	}

 

위와 같이 루프를 돌며 연결 상태, 마지막으로 접속이 끊어진 시간을 비교하여 연결을 걸 수 있는 조건이라고 판단되면 PostAccept 함수에서 연결을 예약한다.

여기서 조건을 체크할 때 continue 문으로 부합하는 조건을 건너뛰는 것이 흥미로운 것 같다.

일전에 이렇게 조건을 건너뛰는 코드를 본적이 있는데 상당히 흥미롭게 느끼고 애용 중이다.

 

그런데 해당 작업을 Accept 쓰레드에서 한번 씩 모두 예약을 걸고나면 호출할 일이 없지 않는가? 라는 의문을 갖게 되었다.

이렇게 되면 모든 Accept 작업을 예약하고나면 접속이 끊기고 다시 연결을 예약할 때까지는 쓰레드가 무한루프를 돌며 리소스를 차지하게 될 것 같다는 예상이다.

 

해당 부분은 모든 실습을 마치고 한번 Accept 예약이 더 이상 필요하지 않을 때 해당 쓰레드가 다른 역할도 할 수 있도록 프로그램을 수정해봐할 것 같다.

 

다시 실습으로 돌아와서 모든 예약을 마치고나면 진행해야할 작업은 바로 완료 처리이다.

완료처리의 순서는 다음과 같다.

  1. IOCP 오브젝트에서 완료 정보를 받는다.
  2. IOCPOperation이 ACCEPT라면 accept 완료처리를 시작한다.
  3. 연결된 클라이언트를 IOCP 오브젝트에 등록해주면 완료이다.

완료 정보를 받고 해당 클라이언트를 IOCP에 등록해주면 되는 간단한 작업이다.

 

다음으로 클라이언트가 연결 종료되었을 때 해주어야할 처리이다.

  1. 마지막으로 접속 종료된 시간을 저장한다.
  2. isConnected 값을 0으로 설정한다. (꺼준다)

여기까지 설계하고 구현하면 정상적으로 동작할 줄 알았지만 몇몇가지의 에러가 발생했다.

해당 에러를 고치는 과정을 기록해보도록 하겠다.

 

클라이언트가 접속하고 바로 연결이 끊기는 버그가 있었다.

우선 클라이언트 연결을 종료하는 Close 함수에 중단점을 걸어보았다.

이후 해당 CloseSocket 함수가 어디서 호출되었는지를 찾아보았다.

호출 스택을 살펴보면 된다!! - 여기서 WokerThread에서 호출된 것을 알 수 있다.

호출 스택의 WokerThread 부분을 더블 클릭하면 해당 함수로 바로 이동된다.

여기서 완료 처리를 받는데 까지는 확실히 성공했다는 것을 알 수 있다.

사실 여기서 해당 이미지를 보고 눈치빠른 분은 어디서 실수했는지 알 수 있을 것 같다.

그런데 나는 여기서 에러를 찾진 못하였고,,, 이후에 발견하였다.

 

에러 찾는 여정으로 돌아와서 AcceptCompletion 함수에서는 IOCP 핸들에 클라이언트 소켓을 등록하고 있다.

이곳에서 에러가 발생하지 않았나? 싶어서 해당 코드를 한줄 씩 타고 들어가봤지만 아무런 문제도 없었다.

 

그리고 뒤늦게.... AcceptCompletion 함수의 성공(true) 여부를 거꾸로 체크(false로 체크)하고 있었다는 것을 찾을 수 있었다..

한시간 정도는 해당 오류를 찾으려고 고생한 것 같았지만 에러의 원인을 알고나니 단순히 내 실수에서 비롯된 것을 깨달을 수 있었다.

해당 오류를 수정하기 위해 (== false) 부분을 지워주었고 실행시킨 결과, 제대로 Echo 서버 프로그램이 동작하는 것을 확인할 수 있었다.

 

마지막으로 깃허브 주소를 남기고 포스팅을 마치도록 하겠다.

https://github.com/MyungHyun-Ahn/Server-Programming-Study/tree/main/IOCP_STUDY/IOCP_07

반응형