dh_0e

[C++/Game Server] SendBuffer (+GameSession) 본문

C++/Game Server

[C++/Game Server] SendBuffer (+GameSession)

dh_0e 2026. 5. 6. 10:48

SendBuffer

  • 목적
    • 메모리 파편화 해결: 작은 패킷을 보낼 때마다 메모리를 할당/해제하면 시스템 성능이 저하됨
    • 할당 비용 감소: 고성능 서버에서는 매 초당 수만 개의 패킷이 오가는데, 그때마다 커널에 메모리를 요청하는 것은 비효율적임
    • 수명 보장: 비동기 I/O(IOCP) 특성상 WSASend가 완료될 때까지 버퍼가 살아있음을 보장해야 함
  • 전송 흐름
    1. Send(): 버퍼를 당장 보내는 것이 아니라 _sendQueue에 일단 담고 RegisterSend가 이미 돌아가고 있다면 종료
    2. RegisterSend(): RegisterSend가 준비(_sendRegistered==false)됐다면 큐에 쌓인 모든 버퍼를 _sendEvent의 sendBuffers 벡터에 옮김
      • 이때, Scatter-Gather를 위해 wsaBufs 배열을 준비해서 버퍼 데이터들의 정보를 담아줌
      • Scatter-Gather I/O: 한 번에 묶어 보내기
        • 여러 개로 흩어져 있는 메모리 블록(SendBuffer)을 한 번의 시스템 콜(WSASend)로 모아서 보내는 기법
        • 패킷이 생길 때마다 전송 함수를 호출하는 대신, 큐에 쌓인 것들을 한 번에 처리하므로 커널 모드 전환 비용을 줄여줌
    3. WSASend()로 버퍼 데이터 전송 요청
    4. Dispatch(): 전송이 완료되면 ProcessSend에서 RELEASE_REF 후에 OnSend() 호출
    5. ProcessSend(): _sendQueue가 그새 찼다면 RegisterSend() 호출 / 아니면 _sendRegistered false로 변경해서 다음 Send가 RegisterSend를 호출할 수 있게 해 줌
  • Buffer Data Structure: 모두 SendBufferRef로 관리해서 이동 연산 비용도 아끼고, owner 처리Ref Count도 적용  
    • Queue<SendBufferRef> Session::_sendQueue: 보내려고 줄 서 있는 버퍼 데이터 큐
    • Vector<SendBufferRef> SendEvent::sendBuffers: 시스템 콜로 보내고 있는 중이라서, 절대 메모리에서 사라지면 안 되는 데이터
      • 참조 횟수 유지, 완료 시점까지 보존 역할을 하며, ProcessSend에서 마지막에 clear 되며 SendBuffer 소멸자 발동
    • Vector<WSABUF> wsaBufs: OS에게  데이터 버퍼의 길이(len)와 포인터(*buf)를 알려주기 위한 주소록

 

SendBuffer.h

#pragma once

/*------------------
	SendBuffer
-------------------*/

class SendBuffer : enable_shared_from_this<SendBuffer> {
public:
	SendBuffer(int32 bufferSize);
	~SendBuffer();

	BYTE* Buffer() { return _buffer.data(); }
	int32 WriteSize() { return _writeSize; }
	int32 Capacity() { return static_cast<int32>(_buffer.size()); }

	void CopyData(void* data, int32 len);

private:
	Vector<BYTE> _buffer;
	int32 _writeSize;
};

 

SendBuffer.cpp

#include "pch.h"
#include "SendBuffer.h"

/*------------------
	SendBuffer
-------------------*/

SendBuffer::SendBuffer(int32 bufferSize) {
	_buffer.resize(bufferSize);
}

SendBuffer::~SendBuffer() {}

void SendBuffer::CopyData(void* data, int32 len) {
	ASSERT_CRASH(Capacity() >= len);
	::memcpy(_buffer.data(), data, len);
	_writeSize = len;
}

 

CorePch.h / IocpEvent.h / Session.h / Session.cpp / Types.h 수정

 

 

SendBuffer · znfnfns0365/CPP_Server_Practice@978def9

- if (SOCKET_ERROR == ::WSASend(_socket, &wsaBuf, 1, OUT & numOfBytes, 0, sendEvent, nullptr)) {

github.com

  • CorePch.h: SendBuffer 헤더 추가
  • IocpEvent.h: WSASend 완료 시점까지 데이터를 보존하는 vector인 sendBuffers 선언
  • Session.h: _sendQueue, _sendRegistered, _sendEvent 선언
  • Session.cpp 
    • Send(): _sendQueue에 버퍼 삽입 로직, _sendRegistered를 확인하여 RegisterSend() 호출
    • RegisterSend(): _sendQueue의 버퍼를 sendBuffers로 옮겨주고 wsaBufs로 주소록 저장 및 WSASend 호출
    • ProcessSend(): RELEASE_REF, 다음 RegisterSend를 바로 호출할지 호출 대기시킬지 결정
  • Types.h: SendBufferRef 정의

 

GameSession, GameSessionManager 생성 및 initialize

 

GameSession, GameSessionManager Class 생성 및 initialize · znfnfns0365/CPP_Server_Practice@66e221c

+ MakeShared<ClientService>(NetAddress(L"127.0.0.1", 7777), MakeShared<IocpCore>(), MakeShared<ServerSession>, 5);

github.com

  • GameSession: 각 클라이언트마다 하나의 GameSession 객체 할당
    • OnRecv(), OnSend()로 데이터 송수신 로직 관리
    • OnConnected(), OnDisconnected()로 연결/해제 로직 관리
  • GameSessionManager: 서버에 연결된 모든 GameSessionRef를 보관하는 컨테이너 (보통 Set이나 Map을 사용)
    • Add(), Remove()로 세션 추가/ 제거
    • Broadcast()로 모든 유저에게 패킷 전송
    • GameSessionRef(shared_ptr)로 관리되므로, 매니저가 들고 있는 동안에는 세션 객체가 메모리에서 사라지지 않도록 보장함

DummyClient / GameServer 종료 시

  • 두 상황에서 원활한 통신과 클라이언트/서버 종료 시 GameSession/ServerSession 소멸자가 잘 작동하는 것을 확인할 수 있음