bdfgdfg

통신 규칙정의, 패킷제작 본문

게임프로그래밍/서버 책

통신 규칙정의, 패킷제작

marmelo12 2021. 12. 16. 13:51
반응형

프로토콜(Protocol)

- 네트워크에서의 프로토콜은 두 개체간의 통신 규약을 의미.

 -> 어떤 행위를 어떻게 요청하고 명령하는지를 정한 일종의 약속

 -> TCP와 UDP도 프로토콜(하나의 통신규약)

 -->다만 TCP와 UDP는 응용계층의 아래인 전송계층단계(즉 하위단계)에서 작동되어지므로 신경을 써줄 필요가 없다.

 -> 이 외에도 두 프로그램 사이에서 어떤 행위를 요청하거나 명령하기 위한 규칙을 정하는데 이것을 프로토콜이라한다.

 

패킷(Packet)

- 네트워크에서 정의하는 패킷은 통신에서 하나의 데이터가 오고 가는 단위를 의미.

- 서버와 클라잉너트는 수많은 작업 요청과 명령이 오가면서 통신을 이룸.

 -> 우리는 이러한 통신을 위해 프로토콜을 정의하고 그것을 전송할 수 있도록 포장을 해주어야 한다.

 

패킷 디자인

패킷에는 기본적으로 헤더정보를 가지고 있어야한다.

 - 어떤 데이터를 구성하고 정의하는 단위의 앞에서 반복적이고 표준하되어지는 내용을 의미

 - 일반적으로 데이터의 종류,크기/개수등으로 구성

 - 프로그램이 데이터를 읽기에 앞서 이 헤더를 읽음으로써 어떤 데이터가 있는지를 판단할 수 있음.

 

패킷의 헤더

프로토콜 ID        - 어떤 명령인지를 의미한다.

패킷 크기(Bytes) - 이 패킷의 전체 크기를 의미한다. 수신할 때 얼마나 읽어야 하는지 판단할 때 쓰인다.

 -> 패킷 크기를 통해 TCP의 경계 처리를한다.(TCP는 경계의 개념이 없음)

 

패킷은 이러한 헤더를 갖고있어야 함.

(목적지에서 패킷을 수신할 때 필요한 정보와 어떤 패킷인지를 판단해야하기 때문)

패킷은 네트워크를 통해 빈번하게 주고받게 되는데 이 패킷의 크기가 커지면 처리속도가 느려질 수 밖에없음.

-> 이러한 문제를 해결하기 가장 쉬운 방버은 패킷의 정보를 최소화 시키는 것. (패킷의 헤더크기 줄이기)

 

 

패킷은 헤더 이외에도 데이터를 담을 데이터 영역이 존재한다. 이것또한 패킷의 크기를 줄이기 위해 데이터를 최소화 해야한다.

 

패킷 클래스

#pragma once
#include "std.h"


#define		PACKETBUFFERSIZE	8192 // 패킷의 크기는 8192바이트로 제한.
#define		PACKETHEADERSIZE	4
/*
패킷에 대한 기본적인 정보(헤더정보)
프로토콜 ID : 어떤 명령인지를 의미.
패킷 크기(Bytes) : 이 패킷의 전체 크기를 의미한다. 수신할 때 얼마나 읽어야 하는지 판단할 떄 쓰임.
*/ 
class Packet
{
public:
	Packet();
	Packet(unsigned short idValue);
	Packet(const Packet& source); // 복사생성자.
	virtual ~Packet();
public:
	bool			IsValidHeader();
	Packet&			Id(unsigned short ID);
	unsigned short  Id();
	void			CopyToBuffer(char* buffer, int size);
	unsigned short  GetDataFieldSize();
	int				GetPacketSize() { return (GetDataFieldSize() + PACKETHEADERSIZE); }
	int			    GetReceivedSize() { return m_receivedSize; }
	char*			GetPacketBuffer() { return m_packetBuffer; }
	void Clear();
	void WriteData(void* buffer, int size);
	void ReadData(void* buffer, int size);
public:
	//  Packet I/O operators
	Packet& operator = (Packet& packet);

	Packet& operator << (bool arg);
	Packet& operator << (int arg);
	Packet& operator << (long arg);
	Packet& operator << (DWORD arg);
	Packet& operator << (__int64 arg);
	Packet& operator << (LPTSTR arg);
	Packet& operator << (Packet& arg);

	Packet& operator >> (bool& arg);
	Packet& operator >> (int& arg);
	Packet& operator >> (long& arg);
	Packet& operator >> (DWORD& arg);
	Packet& operator >> (__int64& arg);
	Packet& operator >> (LPTSTR arg);
	Packet& operator >> (Packet& arg);
protected:
	struct Header // 패킷의 헤더영역.(내부에서만 사용가능하도록)
	{
		unsigned short* m_protocollID;
		unsigned short* m_dataSize;
	};
	Header  m_packetHeader;
private:
	char	m_packetBuffer[PACKETBUFFERSIZE]; // 패킷에 담긴 데이터 버퍼영역
	char*	m_dataField;
	char*	m_endOfDataField;
	char*	m_readPosition;
	char*	m_writePosition;
	int		m_receivedSize;

};

 

#include "Packet.h"

Packet::Packet() : m_packetHeader(), m_packetBuffer(),m_dataField(0),m_endOfDataField(0),m_readPosition(0)
,m_writePosition(0)
{
	Clear(); // 초기화.
}

Packet::Packet(unsigned short idValue) : m_packetHeader(), m_packetBuffer(), m_dataField(0), m_endOfDataField(0), m_readPosition(0)
, m_writePosition(0)
{
	Clear();
	Id(idValue); // Packet ProtocollId저장.
}

Packet::Packet(const Packet& source) : m_packetHeader(), m_packetBuffer(), m_dataField(0), m_endOfDataField(0), m_readPosition(0)
, m_writePosition(0)
{
	Clear();
	::CopyMemory(m_packetBuffer, source.m_packetBuffer, PACKETBUFFERSIZE);
	//패킷버퍼를 복사해서 가져온다.

	// 위치값을 DWORD(정수)값으로 뽑아와서 (버퍼는 배열로 이어져있으므로.) 각각 읽기,쓰기위치 구함
	DWORD offset;
	// 읽기 위치는 읽기위치에서 dataField를 뺀값.
	offset = (DWORD)source.m_readPosition - (DWORD)source.m_dataField; 
	m_readPosition += offset;
	offset = (DWORD)source.m_writePosition - (DWORD)source.m_dataField;
	m_writePosition += offset;
}

Packet::~Packet()
{
}

bool Packet::IsValidHeader()
{
	// 현재 패킷사이즈가 headersize보다 크다면 true.
	// (크기가 최소 4바이트 이상이여야 한단 의미)
	if (GetPacketSize() >= PACKETHEADERSIZE)
		return true;
	return false;
}

Packet& Packet::Id(unsigned short ID)
{
	*m_packetHeader.m_protocollID = ID;
	return *this;
}

unsigned short Packet::Id()
{
	return *m_packetHeader.m_protocollID;
}

void Packet::CopyToBuffer(char* buffer, int size)
{
	Clear();
	// 네트워크로부터 패킷을 수신할때 사용.
	// 수신 받은 패킷을 packetBuffer에 강제로 복사.
	::CopyMemory(m_packetBuffer, buffer, size);
	// buffer에 있는 데이터를 packetBuffer로 복사.
	m_receivedSize += size;
}

unsigned short Packet::GetDataFieldSize()
{
	return *m_packetHeader.m_dataSize;
}

void Packet::Clear()
{
	// packetBuffer에는 패킷의 헤더부터 실제 데이터 영역까지 모두 저장되어야함
	::ZeroMemory(m_packetBuffer, sizeof(m_packetBuffer));

	// packetHeader의 dataSIZE는 버퍼의 맨앞의 0 ~ 1 공간 (2바이트)
	m_packetHeader.m_dataSize = reinterpret_cast<unsigned short*>(m_packetBuffer + 0); // +0은 굳이 필요없음
	// packetHeader의 ProtocollId는는 버퍼의 맨앞의 2 ~ 3 공간 (2바이트)
	m_packetHeader.m_protocollID = reinterpret_cast<unsigned short*>(m_packetBuffer + 2);
	m_dataField = m_packetBuffer + 4; // 데이터필드는 +4바이트 공간부터 시작.
	m_readPosition = m_writePosition = m_dataField; // 마찬가지.(처음)

	m_endOfDataField = &m_dataField[PACKETBUFFERSIZE - 1];

	Id(0);
	m_receivedSize = 0;
}

void Packet::WriteData(void* buffer, int size)
{
	// 더이상 쓸공간이없다면 종료
	if (m_writePosition + size > m_endOfDataField)
		return;

	::CopyMemory(m_writePosition, buffer, size);
	// 써야할 위치에 buffer의 데이터를 size만큼 넣어준다.
	m_writePosition += size; // 써야할 위치 이동.
	*m_packetHeader.m_dataSize += size; // 데이터 크기 증가.
}

void Packet::ReadData(void* buffer, int size)
{
	// 1. 데이터영역부터 현재쓴공간 이상을 읽으려고한다면 x
	// 2. 버퍼의 크기를 넘어서 읽을려고한다면 x.

	if (m_readPosition + size > m_dataField + GetDataFieldSize() ||
		m_readPosition + size > m_endOfDataField)
		return;
	::CopyMemory(buffer, m_readPosition, size);
	// readPosition으로부터  size만큼 buffer에 데이터를 담는다.
	m_readPosition += size;
	// 읽을 위치 옮기기.
}

////////////////////////////////////////////////////////////////////////////
//  Operators

Packet& Packet::operator = (Packet& packet)
{
	::CopyMemory(m_dataField, packet.m_dataField, packet.GetDataFieldSize());

	*m_packetHeader.m_protocollID = packet.Id();
	*m_packetHeader.m_dataSize = packet.GetDataFieldSize();

	return *this;
}

Packet& Packet::operator << (LPTSTR arg)
{
	WriteData(arg, lstrlen(arg) * sizeof(TCHAR) + sizeof(TCHAR));

	return *this;
}

Packet& Packet::operator >> (LPTSTR arg)
{
	ReadData(arg, lstrlen((LPTSTR)m_readPosition) * sizeof(TCHAR) + sizeof(TCHAR));

	return *this;
}

Packet& Packet::operator << (Packet& arg)
{
	unsigned int idValue = arg.Id();
	unsigned int size = arg.GetDataFieldSize();

	WriteData(&idValue, sizeof(unsigned int));
	WriteData(&size, sizeof(unsigned int));
	WriteData(arg.m_dataField, size);

	return *this;
}

Packet& Packet::operator >> (Packet& arg)
{
	int idValue, size;
	char buffer[PACKETBUFFERSIZE];

	ReadData(&idValue, sizeof(int));
	ReadData(&size, sizeof(int));

	ReadData(buffer, size);

	arg.Id(idValue);
	arg.WriteData(buffer, size);

	return *this;
}

Packet& Packet::operator << (bool arg)
{
	WriteData(&arg, sizeof(bool));

	return *this;
}

Packet& Packet::operator >> (bool& arg)
{
	ReadData(&arg, sizeof(bool));

	return *this;
}


Packet& Packet::operator << (int arg)
{
	WriteData(&arg, sizeof(int));

	return *this;
}

Packet& Packet::operator >> (int& arg)
{
	ReadData(&arg, sizeof(int));

	return *this;
}

Packet& Packet::operator << (long arg)
{
	WriteData(&arg, sizeof(long));

	return *this;
}

Packet& Packet::operator >> (long& arg)
{
	ReadData(&arg, sizeof(long));

	return *this;
}

Packet& Packet::operator << (DWORD arg)
{
	WriteData(&arg, sizeof(DWORD));

	return *this;
}

Packet& Packet::operator >> (DWORD& arg)
{
	ReadData(&arg, sizeof(DWORD));

	return *this;
}

Packet& Packet::operator << (__int64 arg)
{
	WriteData(&arg, sizeof(__int64));

	return *this;
}

Packet& Packet::operator >> (__int64& arg)
{
	ReadData(&arg, sizeof(__int64));

	return*this;
}

 

 

 

 

 

클릭하세요 온라인 게임 네트워크 프로그래밍을 참고하였습니다.

반응형
Comments