LOGIN • JOININ

Send and Receive

[Tutorials/[Network] 1. Socket classes/Server/Tutorial_SocketServer02_SendReceive]



1... 접속 시 메시지 전송 하기 (OnConnect)

접속  할때 어떤 작업을 하도록 작성하고 싶다면 OnConnect()함수를 재정의해 작성해 넣으면 됩니다.

따라서 접속할 때 메시지를 전송하고자한다면 OnConnect()를 재정의해 메시지를 전송하는 코드를 넣으면 될 것입니다.

class CSocket : public CGNet::Socket::CTCP<>
{
	virtual void OnConnect() override

	virtual void OnDisconnect(uint32_t) override	{}

	virtual int OnMessage(CGMSG& _MSG) override;
};

  OnConnect()함수에 아래와 같이 메시지를 전송하도록 작성했습니다. 


void CSocket::OnConnect()
{
	// 1) 전송을 위해 버퍼를 할당받는다.
	CCGBuffer	bufTemp	 = MEM_POOL_ALLOC(32);

	// 2) 전송할 데이터들을 써넣는다.
	bufTemp.append<uint32_t>(12);
	bufTemp.append<uint32_t>(10);
	bufTemp.append<int>(100);

	// 3) 전송한다.
	Send(bufTemp);
}

CCGBuffer에 메모리를 할당 받아 메시지를 작성하고 Send()함수를 호출해 메시지를 전송했습니다.

물론 CCGBuffer에 메시지를 작성하는 것은 아래처럼 좀더 간단하게 작성이 가능합니다.

void CSocket::OnConnect()
{
	// 1) 한 줄로 가능하다.
	Send(CCGBuffer(MEM_POOL_ALLOC(32))<<uint32_t(32)<<(10)<<(100));
}



2... 메시지 전송되어 오면 처리하기 (OnMessage)

메시지를 받게 되면  OnMessage()함수가 호출되어 수신한 메시지를 전달해 옵니다.
따라서 OnMessage()함수에서 받은 메시지를 처리하도록 작성해 주면 될 것입니다.

int CSocket::OnMessage(CGMSG& _MSG)
{
	// 1) _MSG.message가 CGNETMESSAGE인 경우만 상대에게 전송되어온 메시지이다.
	if(_MSG.message==CGNETMESSAGE)
	{
		// 2) CGNETMSG로 Casting한다.
		CGNETMSG&	rMSG	 = (CGNETMSG&)_MSG;

		// 3) 메시지 데이터의 제일 앞에서 4Byte 떨어진 uint32_t가 message 종류다.
		uint32_t	uMessage	 = rMSG.Buffer.front<uint32_t>(4);

		// 4) message에 따른 처리를 한다.
		switch(uMessage)
		{
		case	MESSAGE_A:
				OnMessage_A(rMSG);
				break;

		case	MESSAGE_B:
				OnMessage_B(rMSG);
				break;

		case	MESSAGE_C:
				OnMessage_C(rMSG);
				break;
		}
	}

	return	0;
}

1) OnMessage에서는 소켓을 통해 네트워크로 전송되어온 메시지뿐만 아니라 다양한 경로로 메시지를 전달받을 수 있기 때문에 먼저 네트워크를 전송된 메시지인지를 먼저 확인해야 합니다. _MSG.message 값이 ‘CGNETMESSAGE’인가를 확인하면 알 수 있습니다.
2) 네트워크 메시지란 것이 확인되면 _MSG를 CGNETMSG로 캐스팅합니다. 'CGNETMESSAGE'라면 _MSG는 원래 CGNETMSG로 생성해 전달한 것이기 때문에 캐스팅이 가능합니다.

CGNETMSG 구조체의 Buffer 변수에 송신된 메시지 데이터가 들어 있습니다.
3) 위의 예제에 사용된 프로토콜은 메시지 데이터 제일 앞 4바이트는 메시지의 크기, 그 다음 4Byte에 메시지의 종류를 나타냅니다. (프로토콜은 서버에 따라 완전히 다르게 설졍될 수 있습니다.)
따라서 메시지 데이터의 4바이트 Offset이 떨어진 곳에서 uint32_t형으로 값을 읽으면 그것이 메시지의 종류가 됩니다. .
4) 따라서 이렇게 얻어진 메시지 종류(uMessage)에 따라 적절한 처리를 해주면 됩니다.


3... 메시지 읽어 내기

CGNETMSG의 Buffer를 통해 전달된 메시지는 바이트들의 배열로 전달됩니다.
따라서 정의된 프로토콜 대로 적절히 읽으면 됩니다. 메시지는 text, binary 등 어떤 형태의 데이터도 가능하며 protobuf, msgpack, json 등을 사용한 메시지의 작성과 읽기도 가능합니다. 
이 예제에서는 MESSAGE_A의 메시지가 아래와 같이 정의되어 있다면 다음과 같이 읽어 들일 수 있습니다.
CGD::buffer의 extract() 혹은 front()함수를 사용해 값을 읽을 수 있습니다.
extract()함수는 버퍼에서 데이터를 순차적으로 읽어 들이며 포인터를 변경시키는 것이고
front()함수는 위치를 지정해 포인터를 변경하지 않고 데이터를 읽을 수 있습니다. 
(자세한 CGD::buffer의 사용법은 CGD::buffer 문서 참고)

int	CSocket::OnMessage_A(CGNETMSG& _MSG)
{
	// 1) 읽어내기 위해 buffer에 임시로 복사.
	CGD::buffer	bufTemp	 = _MSG.Buffer;

	// 2) Message 구조체에서 전송되어온 버퍼에서 데이터를 읽는다.
	auto	size	 = bufTemp.extract<uint32_t>>();
	auto	command	 = bufTemp.extract<uint32_t>();
	auto	value1	 = bufTemp.extract<int>();
	
	// Trace)
	printf("size:%u command:%u value1:%d", size, command, value1);

	return	0;
}

1) extract를 사용하면 포인터를 변경시키므로CGD::buffer를 복사해 놓습니다.
2) 메시지의 프로토콜에 따라 extract()함수를 사용해 값을 읽습니다.

3) 읽은 값을 출력합니다.


[TutorialSimpleServer2.cpp]
#include "stdafx.h"
#include "CGNetSocketTemplates.h"

const uint32_t	MESSAGE_SEND_A				 = 0x00000020;
const uint32_t	MESSAGE_SEND_B				 = 0x00000021;
const uint32_t	MESSAGE_SEND_C				 = 0x00000022;

class CSocket : public CGNet::Socket::CTCP<>
{
	virtual void OnConnect() override;
	virtual void OnDisconnect(uint32_t) override;
	virtual int OnMessage(CGMSG& _MSG) override;
			int	OnMessage_A(CGNETMSG& _MSG);
			int	OnMessage_C(CGNETMSG& _MSG);
};

void CSocket::OnConnect()
{
	// 설명) 접속이 되었을 때 Message를 전송
	printf("Connected\n");

	// 1) 전송을 위해 버퍼를 할당받는다.
	CCGBuffer	bufTemp	 = MEM_POOL_ALLOC(32);

	// 2) 전송할 데이터들을 써넣는다.
	bufTemp.append<uint32_t>(12);	// 이것을 Message의 크기이다! 총 12Byte이므로 12를 써넣는다.
	bufTemp.append<uint32_t>(10);
	bufTemp.append<int>(100);

	// 3) 전송한다.
	Send(bufTemp);
}

void CSocket::OnDisconnect(uint32_t)
{
	printf("Disconnected\n");
}

int CSocket::OnMessage(CGMSG& _MSG)
{
	// 1) _MSG.message가 CGNETMESSAGE인 경우만 상대에게 전송되어온 메시지이다.
	if(_MSG.message==CGNETMESSAGE)
	{
		// 2) CGNETMSG로 Casting한다.
		CGNETMSG&	rMSG	 = (CGNETMSG&)_MSG;

		// 3) 메시지 데이터의 제일 앞에서 4Byte 떨어진 uint32_t가 message 종류다.
		uint32_t	uMessage	 = rMSG.Buffer.front<uint32_t>(4);

		// 4) message에 따른 처리를 한다.
		switch(uMessage)
		{
		case	MESSAGE_SEND_A:
				OnMessage_A(rMSG);
				break;

		case	MESSAGE_SEND_C:
				OnMessage_C(rMSG);
				break;
		}
	}

	return	0;
}

int	CSocket::OnMessage_A(CGNETMSG& _MSG)
{
	// @) 읽어내기 위해 buffer에 임시로 복사.
	CGD::buffer	bufTemp	 = _MSG.Buffer;

	// @) Message 구조체에서 전송되어온 버퍼에서 데이터를 읽는다.
	auto	size	 = bufTemp.extract<uint32_t>();
	auto	command	 = bufTemp.extract<uint32_t>();
	auto	value1	 = bufTemp.extract<int>();
	
	// Trace)
	printf("size:%u command:%u value1:%d", size, command, value1);

	return	0;
}

int	CSocket::OnMessage_C(CGNETMSG& _MSG)
{
	// @) 읽어내기 위해 buffer에 임시로 복사.
	CGD::buffer	bufTemp	 = _MSG.Buffer;

	// @) Message 구조체에서 전송되어온 버퍼에서 데이터를 읽는다.
	auto	size	 = bufTemp.extract<uint32_t>();
	auto	command	 = bufTemp.extract<uint32_t>();
	auto	value1	 = bufTemp.extract<int>();
	auto	value2	 = bufTemp.extract<uint32_t>();
	auto	value3	 = bufTemp.extract<char>();

	// Trace)
	printf("size:%u command:%u v1:%d v2:%u v3:%c", size, command, value1, value2, value3);

	return	0;
}

int _tmain(int /*argc*/, _TCHAR* /*argv*/[])
{
	// 1) Acceptor 객체를 생성한다.
	auto	pacceptor	 = NEW<CGNet::CAcceptor<CSocket>>();

	// 2) 20000번 포트에 Listen을 시작한다.
	pacceptor->Start(20000);

	// Trace)
	printf("Server Start\n");

	// 3) ESC키를 누를 때까지 대기한다.
	while(_getch()!=27);

	// 4) Acceptor를 닫는다.
	pacceptor->Stop();

	return 0;
}