KoreanFoodie's Study
[C++ 게임 서버] 4-7. RecvBuffer 본문
[C++ 게임 서버] 4-7. RecvBuffer
핵심 :
1. RecvBuffer 를 만들 때는, ReadPos / WritePos 와 관련한 처리를 어떻게 할 것인지가 중요하다.
2. 보통 BufferSize 보다 크게 메모리를 할당해, Clean 을 효율적으로 구현하는 방법을 많이 사용한다.
이전에 Recv 는 Send 에 비해 비교적 구조가 간단하다고 말한 적이 있다. 왜냐면... Send 는 여러 군데에서 동시에 호출하고 난리를 치지만, Recv 는 결국 멀티 쓰레드 환경일지라도 동시에 받는 것이 아니라 순차적으로 받게 되어 있으므로(논리상), 상대적으로 얌전(?) 할 수 밖에 없는 것이다 😅
물론, Recv 의 경우에도 RecvBuffer 를 어떻게 만들어야 좋을지에 대해서는 고민이 약간 필요한데, 이 글에서는 RecvBuffer 를 아래와 같이 구현할 것이다 :
- 버퍼의 크기보다 훨씬 큰(e.g. 10 배) 영역을 잡고, Read/Write Pos 를 움직이도록 한다.
- 읽으려는 DataSize 가 0 이 되면(즉, Read/Write Pos 가 일치하면) 두 Pos 를 전부 시작점으로 옮기고, 만약 자투리(Buffer 사이즈보다 작은)가 남으면, 해당 DataSize 만큼을 앞 부분으로 옮기고, Write Pos 도 그에 맞게 옮긴다.
말로 하니까 더 두루뭉술해지는 것 같으니, 코드를 한 번 보자. 😂
// Session.h
/* 수신 관련 */
RecvBuffer _recvBuffer;
먼저, Session.h 에 RecvBuffer 클래스 타입의 _recvBuffer 를 선언할 것이다. 이 클래스는 다음과 같이 생겼는데...
RecvBuffer.h
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;
};
위에서 BUFFER_COUNT 는, 버퍼의 원래 크기에 곱하여 실제 버퍼의 크기를 할당하는 데 사용될 것이다.
OnRead, OnWrite, ReadPos, WritePos 등의 함수는 말 그대로 읽고 쓰는데 사용된다.
그렇다면 위에서 _capacity 와 _bufferSize 는 어떤 관계일까?
RecvBuffer.cpp
#include "pch.h"
#include "RecvBuffer.h"
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;
}
_capacity 는 _bufferSize 에 BUFFER_COUNT 를 곱한 값으로, 실제 사용할 버퍼 자료구조의 최대 크기로 정한다. 이 안에서, _readPos 와 _writePos 가 움직이며 데이터를 읽을 것이다.
사실 제일 중요한 부분이 바로 Clean 함수인데... 이는 위에서 1, 2 로 나누어 설명한 부분에 대한 구현이다! 😉
이제 위의 OnRead 와 OnWrite 함수는 Session 함수에서 다음과 같이 쓰이게 된다 :
void Session::ProcessRecv(int32 numOfBytes)
{
_recvEvent.owner = nullptr; // RELEASE_REF
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();
}
위에서, _recvBuffer 를 통해 numOfBytes 만큼을 읽은 다음, recvBuffer 의 _readPos, _writePos 를 Clean 을 통해 재조정해 주는 것을 확인할 수 있다! 😮
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 4-9. SendBuffer Pooling (0) | 2023.12.14 |
---|---|
[C++ 게임 서버] 4-8. SendBuffer (0) | 2023.12.13 |
[C++ 게임 서버] 4-6. Session #3 (0) | 2023.12.12 |
[C++ 게임 서버] 4-5. Session #2 (0) | 2023.12.07 |
[C++ 게임 서버] 4-4. Session #1 (0) | 2023.12.06 |