세션 객체 풀링 구현하기

이창준, Changjoon Lee·2025년 9월 11일
0

Game Server Hyperion 🎮

목록 보기
14/14

프로젝트 : https://github.com/HoonInPark/ServerHyperion.git
본 포스트에 대한 내용은 feat/framesync 브랜치에 있다.

문제

지금 내 코드는 서버가 시작할 때 Info 객체를 일정수 만큼 할당하고 배열에 넣는다.

vector<stClientInfo*> m_ClientInfos;

그 결과, 브로드캐스팅할 때 해당 객체가 연결된 객체인지 알 수 없고 따라서 if문이 덕지덕지 붙는 문제가 생긴다. (lock도 역겹다)

case MsgType::MSG_GAME:
{
	for (int i = 0; i < m_ClientInfos.size(); ++i)
	{
		if (0 == m_ClientInfos[i]->IsInited()) continue;
		if (pPack->GetSessionIdx() != i)
		{
			SendMsg(i, Size, pStart);
		}
	}

	m_Lock.lock();
	m_pPackPool->Return(pPack);
	m_Lock.unlock();

	continue;
}

해결

역시 풀링을 사용해야.
1. 순회가 빨라야하고,
2. Client Info에 대응되는 클라이언트의 접속이 끊겼을 때 검색/삭제가 빨라야 한다.

따라서 검색에 상수시간이 소요되고
포인터를 순서 없이 담는
unordered_map<UINT32, CliInfo*>을 사용하고자 한다.

unordered_map<UINT32, CliInfo*> m_CliInfoPool;

StartServer(const UINT32) 내에서 호출되는 CreateClient(const UINT32) 내에 다음과 같이 구현.

	void CreateClient(const UINT32 MaxCliCnt)
	{
		m_SendBufArr = new atomic<OverlappedEx*>[MaxCliCnt];
		for (UINT32 i = 0; i < MaxCliCnt; ++i)
			m_SendBufArr[i].store(nullptr, memory_order_relaxed);

		m_MaxCliCnt = MaxCliCnt;

		/*
		m_SendBufVec.reserve(maxClientCount);
		for (UINT32 i = 0; i < maxClientCount; ++i)
			m_SendBufVec.emplace_back(nullptr);
		*/
		
		for (UINT32 i = 0; i < MaxCliCnt; ++i)
		{
			auto pCliInfo = new CliInfo(m_SendBufArr[i]);
			pCliInfo->Init(i, m_IOCPHandle);
			pCliInfo->PostAccept(m_ListenSocket);

			m_CliInfoPool.emplace(i, pCliInfo);
		}
	}

그럼 이제 접속이 들어오면 unordered_map<UINT32, CliInfo*> m_CliInfoPool에서 디큐하여 unordered_map<UINT32, CliInfo*> m_SpawnedCliInfos에 담는 과정을 구현하자.
기존에 구현돼 있던 AccepterThread를 이해하는 건 AccepterThread 이해하기
를 참고.
이게 필요한 때에만 돌도록 수정하는 과정은 AccepterThread 수정하기 참고.

구현한 결과...

세션이 연결이 돼 있으면 unordered_map<CliInfo>에 담으려 했는데,
이젠 세션이 연결이 돼 있고 자신의 세션 아이디도 발급 받았으며, 다른 접속자에게 Replicate가 돼 있으면 담아서 관리하는 식으로 구현했고,
이렇게 하여서 메인 루프의 부하가 가장 많이 걸리는 케이스인 MsgType::MSG_GAME일 때의 브로드캐스팅 로직의 분기문을 없앨 수 있었다.

		while (m_bIsRunProcThread)
		{
			switch (pPack->GetMsgType())
			{
            // other cases...
			case MsgType::MSG_GAME:
			{
				for (const auto& ConnCliInfo : m_SpawnedCliInfos)
				{
					//if (SESSION_STATUS::ST_SPAWNED != ConnCliInfo.second->GetStatus()) continue;
					ConnCliInfo.second->SendMsg(Size, pStart);
				}

				break;
			}
            // other cases...
            }
            // other logics...
        }

메인루프는 빨라졌다.
얼마나 빨라졌는지는 서버 성능 테스트를 확인하길 바란다.
n명의 접속자가 있을 때 가장 안에 있는 루프는 n * n번 SendMsg(...)를 호출하기 전 거쳐야 하는 분기문을 없앴다.

또, 메인 루프에서 순회하는 동안 세션이 풀로 돌아가서 접근할 수 없는 경우가 있었는데,
그 경우는 일단 메인 루프를 끝내고 마지막에 접속이 끊긴 세션을 정리하는 식으로 구현.
이는 접속자 수에 상관 없이 한꺼번에 접속이 끊길 때 서버 다운에서 설명했다.

profile
C++ Game Developer

0개의 댓글