Thinking Different




 

서버 코드 부분의 마지막인 main 함수부분과 CChatServer 클래스를 작성해보겠습니다. 먼저 stdafx.h 헤더파일과 main.cpp부터 살펴봅시다

 

stdafx.h

#pragma once

#include <SDKDDKVer.h>

#include <iostream>
#include <vector>
#include <deque>
#include <algorithm>
#include <string>
#include <vector>
#include <deque>

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

#include "Message.h"
#include "flatbuffers/flatbuffers.h"
#include "protocol_generated.h"

using namespace std;
using boost::asio::ip::tcp;

 

 

main.cpp

#include "stdafx.h"
#include "ChatServer.h"

const unsigned short PORT_NUMBER = 31400;

int main()
{
	CChatServer server(PORT_NUMBER);

	server.Start();
	
	getchar();
	
	server.Stop();

    return 0;
}

 

서버 클래스를 생성하면서 포트번호를 넘겨주면서 서버를 시작해주고, getchar() 입력을 받기 전까지 서버가 구동되며, 키가 입력되면 서버가 종료되도록 구성하였습니다.

 

 

 

다음은 CChatServer 클래스 부분으로 넘어가 봅시다.

 

세션관리를 위한 컨테이너와 채팅방 객체를 생성하고 관리합니다.

먼저 클라이언트 세션이 접속되기 전까지 async accept 비동기 대기합니다. 접속이 되면 세션관리 컨테이너에 추가되며, 세션은 서버와 프로토콜에 맞게 통신됩니다.

 

여기서 가장 중요한 코드는 역시 FlatBuffers를 사용하는 부분인 ProcessPacket 함수입니다.

클라이언트에서 전송받은 decode된 message의 데이터를 flatbuffers 패킷 구조로 역직렬화하여 해석 확인하고 다시 필요한 프로토콜 패킷 구조로 flatbuffers 직렬화하여 전송합니다.

 

 

ChatServer.h

#pragma once
#include "ChatRoom.h"

class CChatServer
{
public:
	CChatServer(const unsigned short PORT_NUMBER);
	~CChatServer();

public:
	void Start();
	void Stop();

	void EndSession(std::shared_ptr<CSession>);
	void ProcessPacket(std::shared_ptr<CSession> pSession, const uint8_t* pData, size_t size);

private:
	void handle_accept();

private:
	boost::asio::io_context m_IoContext;
	boost::asio::ip::tcp::acceptor m_Acceptor;

	boost::shared_ptr<boost::asio::io_service::work> async_work;
	boost::thread_group async_thread;

	std::set<std::shared_ptr<CSession>> m_SessionList;
	CChatRoom m_ChatRoom;
};

 

 

ChatServer.cpp

#include "stdafx.h"
#include "Session.h"
#include "ChatServer.h"

CChatServer::CChatServer(const unsigned short PORT_NUMBER)
    : m_Acceptor(m_IoContext, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), PORT_NUMBER))
{
}

CChatServer::~CChatServer()
{
    m_SessionList.clear();
}

void CChatServer::Start()
{
    async_work.reset(new boost::asio::io_context::work(m_IoContext));
    async_thread.create_thread(boost::bind(&boost::asio::io_context::run, &m_IoContext));

    std::cout << "서버 시작... (Port : " << m_Acceptor.local_endpoint().port() << ")" << std::endl;
	
    handle_accept();
}

void CChatServer::Stop()
{
    async_work.reset();
    m_IoContext.stop();
    async_thread.join_all();

    std::cout << "서버 종료..." << std::endl;
}

void CChatServer::EndSession(std::shared_ptr<CSession> pSession)
{
	std::cout << "클라이언트 접속 종료 (" << pSession->GetName() << ")" << std::endl;

	m_ChatRoom.Leave(pSession);
	pSession->Socket().close();
	m_SessionList.erase(pSession);
}

void CChatServer::handle_accept()
{
	m_Acceptor.async_accept(
		[this](boost::system::error_code error, tcp::socket socket)
		{
			if (!error)
			{
				std::cout << "클라이언트 접속 성공" << std::endl;

				auto pSession = std::make_shared<CSession>(std::move(socket), this);

				m_SessionList.insert(pSession);

				pSession->Start();
			}
			else
			{
				std::cout << "error No: " << error.value() << " error Message: " << error.message() << std::endl;
			}

			handle_accept();
		});
}

void CChatServer::ProcessPacket(std::shared_ptr<CSession> pSession, const uint8_t* pData, size_t size)
{
	// 패킷이 완성되었는지 체크
	auto verifier = flatbuffers::Verifier(pData, size);

	if (fbs::VerifyRootBuffer(verifier))
	{
		auto root = fbs::GetRoot(pData);

		// 패킷 식별 후 처리
		switch (root->packet_type())
		{
			case fbs::Packet_C2S_CONNECT_REQ:	// 클라이언트 접속 요청
			{
				auto raw = static_cast<const fbs::C2S_CONNECT_REQ*>(root->packet());

				flatbuffers::FlatBufferBuilder builder;

				auto data = fbs::CreateS2C_CONNECT_RES(builder, true);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_CONNECT_RES, data.Union());

				builder.Finish(packet);

				pSession->Write(CMessage(builder.GetBufferPointer(), builder.GetSize()));
				pSession->SetName(raw->name()->c_str());

				std::cout << "클라이언트 로그인 성공 Name: " << pSession->GetName() << std::endl;
				break;
			}

			case fbs::Packet_C2S_ENTER_REQ:		// 방 접속 요청
			{
				// 룸 입장
				m_ChatRoom.Enter(pSession);

				flatbuffers::FlatBufferBuilder builder;

				auto name = builder.CreateString(pSession->GetName());
				auto data = fbs::CreateS2C_ENTER_RES(builder, name);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_ENTER_RES, data.Union());

				builder.Finish(packet);

				m_ChatRoom.Write(CMessage(builder.GetBufferPointer(), builder.GetSize()));

				break;
			}

			case fbs::Packet_C2S_LEAVE_REQ:		// 방 퇴장 요청
			{
				m_ChatRoom.Leave(pSession);

				flatbuffers::FlatBufferBuilder builder;

				auto name = builder.CreateString(pSession->GetName());
				auto data = fbs::CreateS2C_LEAVE_RES(builder, name);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_LEAVE_RES, data.Union());

				builder.Finish(packet);

				m_ChatRoom.Write(CMessage(builder.GetBufferPointer(), builder.GetSize()));

				break;
			}

			case fbs::Packet_C2S_CHAT_REQ:		// 채팅 요청
			{
				auto raw = static_cast<const fbs::C2S_CHAT_REQ*>(root->packet());

				flatbuffers::FlatBufferBuilder builder;

				auto name = builder.CreateString(pSession->GetName());
				auto msg = builder.CreateString(raw->message()->c_str());

				auto data = fbs::CreateS2C_CHAT_RES(builder, name, msg);
				auto packet = fbs::CreateRoot(builder, fbs::Packet_S2C_CHAT_RES, data.Union());

				builder.Finish(packet);

				cout << pSession->GetName() << " : " << raw->message()->c_str() << endl;

				m_ChatRoom.Write(CMessage(builder.GetBufferPointer(), builder.GetSize()));
				break;
			}

			default:
				break;
		}
	}
	else
	{
		throw std::runtime_error("Ill-formed message");
	}
}

 

 

 

서버 소스코드

ChatServer.zip
0.01MB