서버의 종류를 에코 서버에서 채팅 서버로 변경
패킷과 유저를 관리하기 위한 PacketManager와 UserManager 및 User 클래스를 추가
네트워크 라이브러리(IOCPServer.h / ClientInfo.h / Define.h) 및 main.cpp는 기존과 동일(단, EchoServer 클래스의 명칭이 ChatServer로 바뀜)
- 데이터 패킷 수신 로직
- [ClientInfo] 클라이언트 등록시 + 비동기 I/O Recv 작업 완료시 BindRecv()에서 WSARecv()
- [IOCPServer] Worker 쓰레드에서 비동기 Recv 작업 완료 감지
- [ChatServer] OnRecieve()
- [PacketManager] ReceivePacketdata()
- [User] SetPacketData()에서 User의 mPacketDataBuffer에 저장
- [PacketManager] EnqueuePacketData()에서 mPacketQueue에 저장
- 데이터 패킷 처리 로직
- [ChatServer] Run()
- [PacketManager] Run()
- [PacketManager] 새로운 쓰레드에서 ProceessPacket()
- [PacketManager] ProcessRecvPacket()
- [PacketManager] ProcessUserConnect() 또는 ProcessUserDisConnect() 또는 ProcessLogin()
- 데이터 패킷 송신 로직
- [PacketManager] ProcessLogin()에서 SendPackeFunc()
- [IOCPServer] SendMsg
- [ClientInfo] SendMsg
- [ClientInfo] SendIO()에서 WSASend()
- [IOCPServer] Worker 쓰레드에서 비동기 Send 작업 완료 감지
- [ClientInfo] SendCompleted()
- 클라이언트 연결 로직
- [ChatServer] Run()
- [IOCPServer] StartServer()
- [IOCPServer] CreateAccepterThread()
- [IOCPServer] 새로운 쓰레드에서 AccepterThread()
- [ClientInfo] PostAccept()에서 AcceptEx()
- [IOCPServer] Worker 쓰레드에서 비동기 Accept 작업 완료 감지
- [ClientInfo] ACceptCompletion()
- [ClientInfo] OnConnect()
- [ChatServer] OnConnect
- [PacketManager] PushSystemPacket() 함수로 SYS_USER_CONNECT 패킷을 전송
- [PacketManager] ProcessUserConnect()
- Packet.h
- 모든 패킷은 패킷크기, 패킷 Id, 패킷 타입을 포함한 패킷 헤더를 가짐
- 패킷의 종류에 따라 패킷 헤더를 상속하면서 추가로 필요한 데이터를 가지도록 구성
- #pragma pack(push, 1)으로 구조체에서 발생할 수 있는 패딩비트(구조체 크기를 가장 큰 자료형 기준으로 배수만큼 만들어주기 위해 추가되는 비트)를 제거
#pragma pack(push, 1)
struct PACKET_HEADER
{
UINT16 PacketLength;
UINT16 PacketId;
UINT8 Type;
};
const UINT32 PACKET_HEADER_LENGTH = sizeof(PACKET_HEADER);
// 로그인 요청
const int MAX_USER_ID_LEN = 32;
const int MAX_USER_PW_LEN = 32;
struct LOGIN_REQUEST_PACKET : public PACKET_HEADER
{
char UserID[MAX_USER_ID_LEN + 1];
char UserPW[MAX_USER_PW_LEN + 1];
};
const size_t LOGIN_REQUEST_PACKET_SIZE = sizeof(LOGIN_REQUEST_PACKET);
struct LOGIN_RESPONSE_PACKET : public PACKET_HEADER
{
UINT16 Result;
};
// 룸 입장 요청
struct ROOM_ENTER_REQUEST_PACKET : public PACKET_HEADER
{
INT32 RoomNumber;
};
struct ROOM_ENTER_RESPONSE_PACKET : public PACKET_HEADER
{
INT16 Result;
};
// 룸 퇴장 요청
struct ROOM_LEAVE_REQUEST_PACKET : public PACKET_HEADER
{
};
struct ROOM_LEAVE_RESPONSE_PACKET : public PACKET_HEADER
{
INT16 Result;
};
// 룸 채팅
const int MAX_CHAT_MSG_SIZE = 256;
struct ROOM_CHAT_REQUEST_PACKET : public PACKET_HEADER
{
char Message[MAX_CHAT_MSG_SIZE + 1] = { 0, };
};
struct ROOM_CHAT_RESPONSE_PACKET : public PACKET_HEADER
{
INT16 Result;
};
struct ROOM_CHAT_NOTIFY_PACKET : public PACKET_HEADER
{
char UserID[MAX_USER_ID_LEN + 1] = { 0, };
char Msg[MAX_CHAT_MSG_SIZE + 1] = { 0, };
};
#pragma pack(pop)
- User.h
- SetPacketData()
- 버퍼에 데이터를 저장할 위치를 mPacketDataBufferWPos, 버퍼에서 꺼내올 위치를 mPacketDataBufferRPos로 설정
- 데이터를 저장할 위치부터 남은 메모리가 저장할 데이터의 크기보다 작은 경우 링버퍼 구조로 적절하게 WPos와 RPos를 조정
- GetPacket()
- 버퍼에 남아있는 remainByte가 패킷 헤더의 크기보다 작을경우 빈 패킷 반환
- 패킷 헤더에 저장된 패킷 크기가 버퍼에 남아있는 remainByte보다 클 경우 빈 패킷 반환
- 정상적인 경우 버퍼에 있는 패킷 데이터들로 새 패킷을 만들어 반환하고, 패킷 헤더에 저장된 패킷 크기만큼 RPos 이동
- SetPacketData()
void SetPacketData(const UINT32 dataSize_, char *pData_)
{
if ((mPacketDataBufferWPos + dataSize_) >= PACKET_DATA_BUFFER_SIZE)
{
auto remainDataSize = mPacketDataBufferWPos - mPacketDataBufferRPos;
if (remainDataSize > 0)
{
CopyMemory(&mPacketDataBuffer[0], &mPacketDataBuffer[mPacketDataBufferRPos], remainDataSize);
mPacketDataBufferWPos = remainDataSize;
}
else
{
mPacketDataBufferWPos = 0;
}
mPacketDataBufferRPos = 0;
}
CopyMemory(&mPacketDataBuffer[mPacketDataBufferWPos], pData_, dataSize_);
mPacketDataBufferWPos += dataSize_;
}
PacketInfo GetPacket()
{
const int PACKET_SIZE_LENGTH = 2;
const int PACKET_TYPE_LENGTH = 2;
short packetSize = 0;
UINT32 remainByte = mPacketDataBufferWPos - mPacketDataBufferRPos;
if (remainByte < PACKET_HEADER_LENGTH)
{
return PacketInfo();
}
auto pHeader = (PACKET_HEADER*)&mPacketDataBuffer[mPacketDataBufferRPos];
if (pHeader->PacketLength > remainByte)
{
return PacketInfo();
}
PacketInfo packetInfo;
packetInfo.PacketId = pHeader->PacketId;
packetInfo.DataSize = pHeader->PacketLength;
packetInfo.pDataPtr = &mPacketDataBuffer[mPacketDataBufferRPos];
mPacketDataBufferRPos += pHeader->PacketLength;
return packetInfo;
}
- ChatServer.h
- Run()
- std::function : Callable("()" 를 붙여 호출할 수 있는 모든 것)을 객체의 형태로 보관할 수 있는 클래스
- m_pPacketManager는 make_unique()를 통해 unique_ptr으로 생성되어 PacketManager 객체에 유일한 소유권을 부여
- Run()
void Run(const UINT32 maxClient)
{
auto sendPacketFunc = [&](UINT32 clientIndex_, UINT16 packetSize, char* pSendPacket)
{
SendMsg(clientIndex_, packetSize, pSendPacket);
};
m_pPacketManager = std::make_unique<PacketManager>();
m_pPacketManager->SendPacketFunc = sendPacketFunc;
m_pPacketManager->Init(maxClient);
m_pPacketManager->Run();
StartServer(maxClient);
}
- ProcessManager.h / cpp
- Init()
- 키값으로 int, 밸류값으로 PROCESS_RECV_PACKET_FUNCTION을 가지는 unordered_map인 mRecvFunctionDictionary를 초기화
- 키값으로는 Packet.h에 정의해둔 PacketId, 밸류값은 PacketManager의 멤버 함수 포인터를 사용
- ProcessPacket()
- DequePacketData() 함수로 패킷 데이터를 가져옴
- ProcessRecvPacket()
- 패킷의 PacketId를 키값으로 하여 mRecvFunctionDictionary의 밸류값을 찾아 그에따른 멤버 함수 실행
- Init()
void PacketManager::Init(const UINT32 maxClient_)
{
//mRecvFuntionDictionary = std::unordered_map<int, PROCESS_RECV_PACKET_FUNCTION>();
mRecvFuntionDictionary[(int)PACKET_ID::SYS_USER_CONNECT] = &PacketManager::ProcessUserConnect;
mRecvFuntionDictionary[(int)PACKET_ID::SYS_USER_DISCONNECT] = &PacketManager::ProcessUserDisConnect;
mRecvFuntionDictionary[(int)PACKET_ID::LOGIN_REQUEST] = &PacketManager::ProcessLogin;
CreateComponent(maxClient_);
}
void PacketManager::ProcessPacket()
{
while (mIsRunProcessThread)
{
bool isIdle = true;
auto packetData = DequePacketData();
if (packetData.PacketId != 0)
{
isIdle = false;
ProcessRecvPacket(packetData.ClientIndex, packetData.PacketId, packetData.DataSize, packetData.pDataPtr);
}
if (isIdle)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
void PacketManager::EnqueuePacketData(const UINT32 clientIndex_)
{
std::lock_guard<std::mutex> guard(mLock);
auto pUser = mUserManager->GetUserByConnIdx(clientIndex_);
auto packetData = pUser->GetPacket();
mPacketQueue.push_back(packetData);
}
PacketInfo PacketManager::DequePacketData()
{
std::lock_guard<std::mutex> guard(mLock);
if (mPacketQueue.empty())
{
return PacketInfo();
}
auto packetData = mPacketQueue.front();
mPacketQueue.pop_front();
return packetData;
}
'개인공부 > IOCP 서버 제작 실습' 카테고리의 다른 글
IOCP 서버 제작 실습 8 (0) | 2023.03.27 |
---|---|
IOCP 서버 제작 실습 7 (0) | 2023.03.25 |
IOCP 서버 제작 실습 5 (0) | 2023.03.23 |
IOCP 서버 제작 실습 4 (0) | 2023.03.22 |
IOCP 서버 제작 실습 3 (0) | 2023.03.22 |
댓글