[메신저 프로그램 ] #4. Stream / 상대경로

게임 서버 프로그래밍/개발일지

2020. 5. 5. 23:41

멀티플레이어 게임 프로그래밍 책을 보고 짰던 Stream 클래스 코드가 제대로 동작하지 않았다.

처음엔 소켓의 read/write 문제인줄 알았는데 (잡다한 오류들이 많이 생겨서..) 전달된 데이터를 제대로 읽을수 없었다.

 

더보기

다른 책을 보고 스트림을 다시 짜봐도 똑같은 현상이 일어났다.

이상한 기호가 출력되었는데.. 사이즈는 제대로 도착한 것으로 보아 소켓 문제는 아니었다.

나중에 몇가지 실험을 해봤는데.. 데이터는 스트림에 맞게 잘 도착했던 것.

다만, 그것을 읽어내는 함수에 문제가 있었던 것.. 결국 지금 코드는 제대로 동작한다.

멀티플레이어 게임 프로그래밍 책에는 bit 단위로 읽어 들였는데, 굳이 그렇게 까지 대역폭을 아껴야하나 싶어서 바이트 단위로 진행하다 코드가 꼬인것.. 그냥 최근에 산 책의 스트림 코드를 이용하기로 했고, 결과는 잘 나왔다.

 

#define STREAM_WRITE(value)\
size_t size = sizeof(value);\
if(CheckWriteBoundary(size) == false ) return;\
memcpy_s((void*)(this->m_stream.data() + this->m_offset)\
,this->size()-this->m_offset,(void*)&value,size);\
m_offset += size;\

#define STREAM_READ(type, retval)\
size_t size = sizeof(type);\
if(CheckReadBoundary(size) == false)\
return;\
Read((void*)retval,size);

#include "Stream.h"

void Stream::Set(UCHAR * pdata, size_t length)
{
	this->m_offset = length;
	memcpy_s((void*)m_stream.data(), m_stream.size(), (const void*)pdata, length);
}

UCHAR * Stream::data()
{
	return m_stream.data();
}

size_t Stream::size()
{
	return m_offset;
}

void Stream::Init()
{
	m_offset = 0;
	m_readPtr = 0;
	ZeroMemory(&m_stream, sizeof(m_stream));
}

bool Stream::CheckWriteBoundary(size_t size)
{
	if (size + m_offset >= sizeof(m_stream))
	{
		assert(FALSE);
		return false;
	}
	return true;
}

void Stream::operator<<(const BOOL & val)
{
	STREAM_WRITE(val);
}

void Stream::operator<<(const UINT8 & val)
{
	STREAM_WRITE(val);
}

void Stream::operator<<(const INT8 & val)
{
	STREAM_WRITE(val);
}

void Stream::operator<<(const UINT16 & val)
{
	STREAM_WRITE(val);
}

void Stream::operator<<(const INT16 & val)
{
	STREAM_WRITE(val);
}

void Stream::operator<<(const UINT32 & val)
{
	STREAM_WRITE(val);
}

void Stream::operator<<(const INT32 & val)
{
	STREAM_WRITE(val);
}

void Stream::operator<<(const std::string & vals)
{
	size_t length = vals.length();
	*this << length;
	for (size_t i = 0; i < length; i++)
	{
		*this << (UINT8)vals[i];
	}
}

void Stream::operator=(Stream & stream)
{
	Set(stream.data(), stream.size());
}

Stream::Stream(UCHAR * pdata, size_t length)
{
	this->Init();
	this->Set(pdata, length);
}

void Stream::Read(void * retval, size_t size)
{
	memcpy_s(retval, size, (void*)(m_stream.data() + m_readPtr), size);
	m_readPtr += size;
}

bool Stream::CheckReadBoundary(size_t size)
{
	if (size + m_readPtr >= m_offset)
	{
		assert(FALSE);
		return false;
	}

	return true;
}

void Stream::operator>>(BOOL* retval)
{
	STREAM_READ(BOOL, retval);
}

void Stream::operator>>(UINT8 * retval)
{
	STREAM_READ(UINT8, retval);
}

void Stream::operator>>(INT8 * retval)
{
	STREAM_READ(INT8, retval);
}

void Stream::operator>>(UINT16 * retval)
{
	STREAM_READ(UINT16, retval);
}

void Stream::operator>>(INT16 * retval)
{
	STREAM_READ(INT16, retval);
}

void Stream::operator>>(UINT32 * retval)
{
	STREAM_READ(UINT32, retval);
}

void Stream::operator>>(INT32 * retval)
{
	STREAM_READ(INT32, retval);
}

void Stream::operator>>(std::string * retvals)
{
	size_t size;
	*this >> &size;
	if (CheckReadBoundary(size) == false)
		return;

	char* buf = new char[size + 1];
	this->Read((void*)buf, size);
	buf[size] = '\0';

	retvals->clear();
	*retvals = buf;
	delete buf;
}

전문을 올리기는 조금 그렇고.. 아무튼 기존의 코드와의 차이점은 Stream을 하나로 통합시켰다는 점과 

operator를 이용해서 직관적으로 read/write를 구현했다는 점.. 그리고 문제가 됐던 string (char*) 데이터 전달과

수신 과정의 코드가 제대로 고쳐졌다는 점이 있다.

 

결국 혼자서 구현하지 못한 것에 대해서는 나 자신이 한심하기도 하지만.. 지금은 남의 코드라도 보고 이해하면서 앞으로 나아가야한다고 생각한다. 할 것들이 산더미인데 여기서 발이 묶여서는 안되기 때문이다. (비겁한 변명이다.)

 

이제 패킷도 제대로 수신하고 송신하기 때문에 클라이언트를 제대로 작성할 때가 됐다. UI를 이용하면 thread로 이벤트 형식으로 패킷을 송수신 할테니 더 편할 것 같다.

 

그리고, Inc 폴더 때문에 골치를 썩였는데.. 집에서 git으로 프로젝트를 복제하여 쓰다보니 경로가 바뀌는 바람에 상대경로로 고치기로 했었다. pc에서는 문제가 없었는데 노트북에서는 상대경로 지정이 안됐다.

 

구글을 뒤적뒤적거리다가 결국 답을 찾아냈는데! .vs 폴더를 지우고 다시 경로 설정하니까 잘되더라!

 

참고로 프로젝트 파일 경로는 $(ProjectDir) 이다. 추가 포함 디렉터리에 매크로에 다양한 파일 경로 매크로가 존재하니 참고하면 되겠다!