[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버를 참고하여 정리한 내용입니다.
학교 다니고, 회사 다니고.. 바빠서 벨로그 정리를 자주 못한다.. 스터디는 하고 있지만 복기할겸 정리하고 있다..
지난번에 Session에 대해 정리를 했다. IocpEvent에 따라 Dispatch해주는 작업까지 해줬으니 이제 실제로 데이터를 받아오는 작업을 해보려고 한다. 하나의 Recv Event에는 하나의 Buffer가 매핑되어야 한다. 아래처럼 RecvBuffer 클래스를 만들 수 있다.
#include "pch.h"
#include "RecvBuffer.h"
/*
RecvBuffer
*/
RecvBuffer::RecvBuffer(int32 bufferSize) : _bufferSize(bufferSize)
{
_capacity = bufferSize * BUFFER_COUNT;
_buffer.resize(_capacity);
}
RecvBuffer::~RecvBuffer()
{
}
void RecvBuffer::Clean()
{
int32 dataSize = DataSize();
if (dataSize == 0)
{
// 읽기+쓰기 커서가 동일한 위치라면 리셋해야 함.
_readPos = _writePos = 0;
}
else
{
// 여유 공간이 버퍼 1개 크기 미만이라면, 데이터 땡겨버리기
if (FreeSize() < _bufferSize)
{
::memcpy(&_buffer[0], &_buffer[_readPos], dataSize);
_readPos = 0;
_writePos = dataSize;
}
}
}
bool RecvBuffer::OnRead(int32 numOfBytes)
{
if (numOfBytes > DataSize())
return false;
_readPos += numOfBytes;
return true;
}
bool RecvBuffer::OnWrite(int32 numOfBytes)
{
if (numOfBytes > FreeSize())
return false;
_writePos += numOfBytes;
return true;
}
#pragma once
/*
RecvBuffer
*/
// [r][][w][][][][][][][][][]
class RecvBuffer
{
enum { BUFFER_COUNT = 10 };
public:
RecvBuffer(int32 bufferSize);
~RecvBuffer();
void Clean();
bool OnRead(int32 numOfBytes);
bool OnWrite(int32 numOfBytes);
BYTE* ReadPos() { return &_buffer[_readPos]; }
BYTE* WritePos() { return &_buffer[_writePos]; }
int32 DataSize() { return _writePos - _readPos; }
int32 FreeSize() { return _capacity - _writePos; }
private:
int32 _capacity = 0;
int32 _bufferSize = 0;
int32 _readPos = 0;
int32 _writePos = 0;
Vector<BYTE> _buffer;
};
설명에 앞서, 커서에 대해 간단한 설명부터 하겠다.
Write커서의 위치까지 Read커서가 버퍼를 처리해야한다고 보면 된다. 예를 들어, Hello라는 메세지를 받았을 때, Write커서는 Hello의 길이, 5에 위치할 것이다. Read커서는 이제 막 처리하기 시작했으니 0에서부터 5에 도착할 때까지 Read커서를 움직이며 버퍼를 처리한다.
생성자에서 수신버퍼의 크기를 선언한다.
Clean : 현재 읽어들인 버퍼의 상황에 따라 데이터를 밀어버린다. 같은 커서에 위치해있다면, 커서 이전의 버퍼들은 모두 읽었다는 의미이므로, 밀어도 되는 것이다. 또한 여유공간을 확보하기 위해 버퍼 한 개의 사이즈보다 현재 여유공간이 적을 때도 밀어버린다. 공통적으로, 이미 읽어낸 버퍼를 쳐낸다고 보면 된다.
OnRead, OnWrite : 위 커서에 대한 설명대로, 커서가 처리될 때마다 값을 증가시킨다.
ReadPos, WritePos : 각 커서들이 위치한 버퍼의 값들을 구한다. 커서를 움직이면서 계속 값을 읽어낼 수 있다.
이를 세션 클래스에서 응용할 수 있다. 등록된 수신 이벤트를 처리하는 과정에서, 받아온 버퍼의 크기만큼 커서를 움직여가며 데이터를 읽을 수 있다.
void Session::ProcessRecv(int32 numOfBytes)
{
// 이거 안 해주면 Session이 소멸이 안됨.
_recvEvent.owner = nullptr; // Release_Ref
// Recv 실패했을 경우
if (numOfBytes == 0)
{
Disconnect(L"Recv 0");
return;
}
if (_recvBuffer.OnWrite(numOfBytes) == false)
{
Disconnect(L"OnWrite Overflow");
return;
}
int32 dataSize = _recvBuffer.DataSize();
// 컨텐츠 코드에서 재정의
int32 processLen = OnRecv(_recvBuffer.ReadPos(), dataSize);
if (processLen < 0 || dataSize < processLen || _recvBuffer.OnRead(processLen) == false)
{
Disconnect(L"OnRead Overflow");
return;
}
// 커서 정리
_recvBuffer.Clean();
// 수신 등록
RegisterRecv();
}
여기서 OnRecv가 중요한데, 나중에 게임서버 클래스를 만들 때 컨텐츠단에서 이를 후크처럼 사용할 수 있다. 즉, "메세지를 받았을 때"라는 이벤트 함수를 만들 수 있다.