프로젝트 : 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(...)
를 호출하기 전 거쳐야 하는 분기문을 없앴다.
또, 메인 루프에서 순회하는 동안 세션이 풀로 돌아가서 접근할 수 없는 경우가 있었는데,
그 경우는 일단 메인 루프를 끝내고 마지막에 접속이 끊긴 세션을 정리하는 식으로 구현.
이는 접속자 수에 상관 없이 한꺼번에 접속이 끊길 때 서버 다운에서 설명했다.