상세 컨텐츠

본문 제목

Packet Session

똑똑한 개발/C++ 게임개발

by 성댕쓰 2023. 4. 16. 21:25

본문

Session.h, Session.cpp

...
/*----------------
  PacketSession
----------------*/

struct PacketHeader
{
	uint16 size;
	uint16 id; // 프로토콜ID (ex. 1=로그인, 2=이동요청)
};

// [size(2)][id(2)][data...]...[size(2)][id(2)][data...]
class PacketSession : public Session
{
public:
	PacketSession();
	virtual ~PacketSession();

	PacketSessionRef GetPacketSessionRef() { return static_pointer_cast<PacketSession>(shared_from_this()); }

protected:
	virtual int32 OnRecv(BYTE* buffer, int32 len) sealed;
	virtual int32 OnRecvPacket(BYTE* buffer, int32 len) abstract;
};
...
void Session::Send(SendBufferRef sendBuffer)
{
    if (IsConnected() == false)
        return;

    bool registerSend = false;
    
    // 현재 RegisterSend가 걸리지 않은 상태라면, 걸어준다
    {
		WRITE_LOCK;

		_sendQueue.push(sendBuffer);

        if (_sendRegistered.exchange(true) == false)
            registerSend = true;
    }

    if (registerSend)
        RegisterSend();
}
...
void Session::Disconnect(const WCHAR* cause)
{
    if (_connected.exchange(false) == false)
        return;

    // TEMP
    wcout << "Disconnect : " << cause << endl;

    RegisterDisconnect();
}
...
void Session::ProcessDisconnect()
{
    _disconnectEvent.owner = nullptr; // RELEASE_REF
    
    OnDisconnected(); // 컨텐츠 코드에서 오버라이딩
    GetService()->ReleaseSession(GetSessionRef());
}
...
/*----------------
  PacketSession
----------------*/

PacketSession::PacketSession()
{
}

PacketSession::~PacketSession()
{
}

// [size(2)][id(2)][data...]...[size(2)][id(2)][data...]
int32 PacketSession::OnRecv(BYTE* buffer, int32 len)
{
    int32 processLen = 0;

    while (true)
    {
        int32 dataSize = len - processLen;
        // 최소한 헤더는 파싱할 수 있어야 한다.
        if (dataSize < sizeof(PacketHeader))
            break;

        PacketHeader header = *(reinterpret_cast<PacketHeader*>(&buffer[processLen]));
        // 헤더에 기록된 패킷 크기를 파싱할 수 있어야 한다.
        if (dataSize < header.size)
            break;

        // 패킷 조립 성공
        OnRecvPacket(&buffer[processLen], header.size);

        processLen += header.size;
    }

    return processLen;
}

GameSession.h, GameSession.cpp

...
class GameSession : public PacketSession
{
public:
	~GameSession()
	{
		cout << "~GameSession" << endl;
	}

	virtual void OnConnected() override;
	virtual void OnDisconnected() override;
	virtual int32 OnRecvPacket(BYTE* buffer, int32 len) override;

	virtual void OnSend(int32 len) override;
};
...
int32 GameSession::OnRecvPacket(BYTE* buffer, int32 len)
{
	PacketHeader header = *((PacketHeader*)&buffer[0]);
	cout << "Packet ID : " << header.id << "Size : " << header.size << endl;

	return len;
}
...

GameServer.cpp

...
#include "GameSessionManager.h"

int main()
{
...
	char sendData[1000] = "Hello World";

	while (true)
	{
		SendBufferRef sendBuffer = GSendBufferManager->Open(4096);
		
		BYTE* buffer = sendBuffer->Buffer();
		((PacketHeader*)buffer)->size = (sizeof(sendData) + sizeof(PacketHeader));
		((PacketHeader*)buffer)->id = 1; // 1 : Hello Msg
		::memcpy(&buffer[4], sendData, sizeof(sendData));
		sendBuffer->Close((sizeof(sendData) + sizeof(PacketHeader)));
		
		GSessionManager.Broadcast(sendBuffer);

		this_thread::sleep_for(250ms);
	}
}

DummyClient.cpp

...
class ServerSession : public PacketSession
{
...
	virtual void OnConnected() override
	{
		//cout << "Connected To Server" << endl;
	}

	virtual int32 OnRecvPacket(BYTE* buffer, int32 len) override
	{
		PacketHeader header = *((PacketHeader*)&buffer[0]);
		//cout << "Packet ID : " << header.id << "Size : " << header.size << endl;

		char recvBuffer[4096];
		::memcpy(recvBuffer, &buffer[4], header.size - sizeof(PacketHeader));
		cout << recvBuffer << endl;

		return len;
	}

	virtual void OnSend(int32 len) override
	{
		//cout << "OnSend Len = " << len << endl;
	}

	virtual void OnDisconnected() override
	{
		//cout << "Disconnected" << endl;
	}
};

 

1. Session::Send 효율적이지 못한 부분은?

- 어느 한 쓰레드가 RegisterSend 호출 중 이라면 WriteLock에서 다른 많은 Send 요청이 대기 할 수 있음. 

 

2. PacketSession 이 필요한 이유는?

- 보낸 모든 데이터를 한 번에 받는 다는 보장이 없는 TCP 특성 때문에, 데이터 모두 받았음을 확인 할 수 있는 수단이 필요.

 

3. PacketSession::OnRecv에서 header가 덜 들어왔거나 header만 들어온 상황은 어떻게 처리하지?

- RegisterRecv() > ProcessRecv() 호출 되면서 아직 들어오지 않은 부분 들어옴.

 

4. 접속 Client의 개수를 1000개로 늘리면, server broadcast에서 crash 나는 이유는 그리고 해결방법?

- sessions iterate 하다가 sessions 에 변화가 생겨서 남. OnDisconnect에서 session remove하고 있음.

- 해결방법 : Disconnect에서 session remove하지 않고 ProcessDisconnect에서 한다.

'똑똑한 개발 > C++ 게임개발' 카테고리의 다른 글

PacketHandler  (0) 2023.05.06
Buffer Helpers  (0) 2023.04.19
SendBuffer Pooling  (0) 2023.04.05
SendBuffer  (0) 2021.12.21
RecvBuffer  (0) 2021.12.20

관련글 더보기

댓글 영역