(C++ : IOCP-53-0) 채팅 실습

Posted by : at

Category : Cpp   iocp


패킷의 설계

// Struct.proto
syntax = "proto3";
package Protocol;

import "Enum.proto";

message Player
{
	uint64 id = 1;
	string name = 2;
	PlayerType playerType = 3;
	// 스탯 정보...
}
// Protocol.proto
syntax = "proto3";
package Protocol;

import "Enum.proto";
import "Struct.proto";

// 서버/클라 로그인 요청
message C_LOGIN
{

}

message S_LOGIN
{
	bool success = 1;
	repeated Player players = 2; // 아이디 발급 전
}

// 서버/클라 게임접속
message C_ENTER_GAME
{
	uint64 playerIndex = 1;
}

message S_ENTER_GAME
{
	bool success = 1;
}

// 채팅
message C_CHAT
{
	string msg = 1;
}

message S_CHAT
{
	uint64 playerId = 1;
	string msg = 2;
}

대략 이렇게 패킷을 정의하고 패킷의 처리는

서버와의 연결 성공 후 클라이언트 로그인시도

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

	virtual void OnConnected() override
	{
		Protocol::C_LOGIN pkt;
		auto sendBuffer = ServerPacketHandler::MakeSendBuffer(pkt);
		Send(sendBuffer);
	}

    // ...

서버에서 로그인 처리

bool Handle_C_LOGIN(PacketSessionRef& session, Protocol::C_LOGIN& pkt)
{
	GameSessionRef gameSession = static_pointer_cast<GameSession>(session);

	// TODO : Validation 체크

	Protocol::S_LOGIN loginPkt;
	loginPkt.set_success(true);

	// DB에서 플레이 정보를 긁어온다
	// GameSession에 플레이 정보를 저장 (메모리)

	// ID 발급 (DB 아이디가 아니고, 인게임 아이디)
	static Atomic<uint64> idGenerator = 1;

	{
		auto player = loginPkt.add_players();
		player->set_name(u8"DB에서긁어온이름1");
		player->set_playertype(Protocol::PLAYER_TYPE_KNIGHT);

		PlayerRef playerRef = MakeShared<Player>();
		playerRef->playerId = idGenerator++;
		playerRef->name = player->name();
		playerRef->type = player->playertype();
		playerRef->ownerSession = gameSession;
		
		gameSession->_players.push_back(playerRef);
	}

	{
		auto player = loginPkt.add_players();
		player->set_name(u8"DB에서긁어온이름2");
		player->set_playertype(Protocol::PLAYER_TYPE_MAGE);

		PlayerRef playerRef = MakeShared<Player>();
		playerRef->playerId = idGenerator++;
		playerRef->name = player->name();
		playerRef->type = player->playertype();
		playerRef->ownerSession = gameSession;

		gameSession->_players.push_back(playerRef);
	}

	auto sendBuffer = ClientPacketHandler::MakeSendBuffer(loginPkt);
	session->Send(sendBuffer);

	return true;
}
bool Handle_S_LOGIN(PacketSessionRef& session, Protocol::S_LOGIN& pkt)
{
	if (pkt.success() == false)
		return true;

	if (pkt.players().size() == 0)
	{
		// 캐릭터 생성창
	}

	// 입장 UI 버튼 눌러서 게임 입장
	Protocol::C_ENTER_GAME enterGamePkt;
	enterGamePkt.set_playerindex(0); // 첫번째 캐릭터로 입장
	auto sendBuffer = ServerPacketHandler::MakeSendBuffer(enterGamePkt);
	session->Send(sendBuffer);

	return true;
}

나머지는 쉽기에 생략…

대략적 현재코드의 문제점을 지적하자면

void Room::Broadcast(SendBufferRef sendBuffer)
{
	WRITE_LOCK;
	for (auto& p : _players)
	{
		p.second->ownerSession->Send(sendBuffer);
	}
}

위 코드와 같이 Room에서 모든 유저에게 브로드캐스트해준다
여기까지는 문제가 없지만… 브로드 캐스트의 방법이 LOCK이라는 것에 문제가 있다.

이후 JobQueue에서 이 문제를 해결해본다


About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links