지금까지는 클라이언트가 그냥 텍스트 메시지를 보내면 서버는 그걸 그대로 돌려주는것만 해봤는데, 이제 본격적으로 프로토버프를 적용시켜 패킷을 주고받을 수 있게 만들어보자.


먼저 Proto 파일을 만들고, protoc를 사용해 c파일과 h파일을 생성하자.

syntax = "proto3";
package Protocol;

// 클라->서버 회원가입 요청
message C_SignUp {
  string email = 1;
  string password = 2;
  string id = 3;
}
// 서버->클라 회원가입 요청 성공/실패
message S_SignUp {
  bool success = 1;
}
// 클라->서버 이메일 인증 요청
message C_VerifyEmail {
  string email = 1;
  string verification_code = 2;
}
// 서버->클라 이메일 인증 요청 성공/실패
message S_VerifyEmail {
  bool success = 1;
}
// 클라->서버 로그인 요청
message C_Login {
  string email = 1;
  string password = 2;
}
// 서버->클라 이메일 인증 요청 성공/실패
message S_Login {
  bool success = 1;
}
protoc --cpp_out=. Protocol.proto

int main()
{
	Protocol::C_SignUp	pkt;
}

다행이 컴파일과 실행 다 잘 됐다. (윈도우에서는 잘 안되서 고생이 많았다)


지금은 메인스레드에서 읽기 가능을 감지한 뒤 ProcessRead 함수를 호출하고, 그안에서 소켓을 read 한다.

그리고 잡을 만들어 잡큐에 넣고 끝내고있다. 잡은 워커스레드가 가져가서 readbuffer의 내용물을 전부 writeBuffer로 옮겨적고 send(writeBuffer)를 호출하는 일을 하도록 했었다. (아래 코드처럼)

void	Session::ProcessRead()
{
	SSL*	ssl = _sslObject->GetSsl();
	do {
		int	readLen = SSL_read(ssl, _readBuffer.ReadPos(), READ_SIZE);
		/* ... */
	} while (SSL_has_pending(ssl));

	JobRef	job = std::make_shared<Job>([this, self_weak = std::weak_ptr<Session>(shared_from_this())](){
		if (std::shared_ptr<Session> session = self_weak.lock())
		{
			WriteBufferRef	writeBuffer = make_shared<WriteBuffer>(_readBuffer.ReadPos(), _readBuffer.DataSize());
			Send(writeBuffer);
			_readBuffer.OnRead(_readBuffer.DataSize());
			_readBuffer.Clean();
		}
	});
	GThreadManager->InsertJob(job);
}

그런데 실제 인증서버에서 read 를 성공한 뒤에 해야 하는 일은 이렇다.

  1. 메인 스레드에서 ReadBuffer가 어떤 패킷인지 확인 -> header 추가 필요
  2. ReadBuffer 중 패킷 부분을 패킷 클래스 객체로 변환 -> 버퍼를 받아서 패킷 클래스 객체를 리턴하는 함수 (ParsePacket)
  3. 객체를 잡큐에 넣고, 워커스레드가 잡큐에서 패킷 클래스 객체를 받음.
  4. 워커스레드는 패킷(ex. C_Login)에 해당하는 DB작업을 한다. -> 패킷에 따라 다른 일 해주는 함수 (HandlePacket)
  5. DB작업 결과를 패킷 클래스 객체(ex. S_Login)에 담고, 이를 직렬화 함 -> 패킷을 WriteBuffer로 만들 함수
  6. WriteBuffer를 Session의 writeBufferQueue에 담으면서 epoll이 세션의 write이벤트를 감지할 수 있게 설정.
  7. 메인스레드의 epoll_wait에서 write 이벤트를 감지하여 writeBufferQueue의 모든 내용을 세션에게 보냄.

이 과정을 수행할 함수들과 구조체를 하나씩 만들어보자.

  • PacketHeader 구조체
  • ParsePacket 함수
  • HandlePacket 함수
  • MakeWriteBuffer 함수

PacketHeader 구조체

많은부분을 게임서버강의에서 배운걸 차용했다.

Session.h (Session 클래스 아래에 배치함)

struct PacketHeader
{
	uint16	size;
	uint16	id;
};

class PacketSession : public Session
{
public:
	PacketSession(int clinetSocket, struct sockaddr_in addr, ServiceRef service);
	virtual ~PacketSession();
protected:
	virtual int		OnRead(BYTE* buffer, int len) final;
	virtual void	OnReadPacket(BYTE* buffer, int len) = 0;
};

PacketHeader 4바이트짜리 구조체를 만든다. size와 id 정보를 헤더에 담을 것.

기존의 세션은 패킷을 관리하는 부분이 없었으니 이 부분을 추가한 패킷세션을 만들어준다. 세션을 상속받는다.

이제 클라이언트 - 인증서버 간의 통신은 모두 패킷세션의 자식클래스로만 수행할 것이다. 기존의 세션은 아예 안쓸 것 같다.

 

void	Session::ProcessRead()
{
	SSL*	ssl = _sslObject->GetSsl();
	do {
		int	readLen = SSL_read(ssl, _readBuffer.ReadPos(), READ_SIZE);
		/* ... */
	} while (SSL_has_pending(ssl));

	int	processLen = OnRead(_readBuffer.ReadPos(), _readBuffer.DataSize());
}
protected:// 컨텐츠 코드에서 오버라이딩
	virtual int		OnRead(BYTE* buffer, int len) { return len; }
	virtual void	OnWrite(int len) {}

OnRead 함수는 ProcessRead 함수 안에서 SSL_read를 성공한 뒤에 호출한다. 앞으로 버퍼 -> 패킷 객체 작업을 수행해야 하니 readBuffer와 readBuffer 의 길이를 인자로 넣어준다. Session클래스의 ProcessRead에서 실행되야하기 때문에 session에도 하나 만들어두자. (게임서버 강의에서는 OnWrite, OnConnect, OnDisconnect도 만들었는데 일단 쓸것같은 OnWrite만 만들어뒀다.)

 

int	PacketSession::OnRead(BYTE* buffer, int len)
{
	int	processLen = 0;

	while (true)
	{
		int	dataSize = len - processLen;

		if (dataSize < sizeof(PacketHeader))
			break;
		struct PacketHeader* header = reinterpret_cast<struct PacketHeader*>(&buffer[processLen]);
		
		if (dataSize < header->size)
			break;

		OnReadPacket(&buffer[processLen], header->size);
		
		processLen += header->size;
	}

	return processLen;
}

OnRead에서 recvBuffer 전체를 받았는데, 여기에 패킷이 하나만 있을수도 있지만 여러개 존재할 수도 있다. OnRead에서 '여기서부터 여기까지가 하나의 패킷' 을 구분지어서 그걸 OnReadPacket 함수에 넣어주면 OnReadPacket에서 해당 패킷을 C_Login과 같은 실제 패킷 객체로 변환, JobQueue에 넣어주는 작업해준다.

 

OnReadPacket이 추상함수인 이유는 지금은 인증서버 - 클라이언트 통신만 하지만, 인증서버 - 게임서버 통신을 하게되면 그 부분에 대한 처리를 다르게 할 수 있으니 구분해놨다. 실제 구현은 PacketSession을 상속받는 GAuthSession이나 CAuthSession에서 하자. 오늘은 CAuthSession만 구현해볼 예정. 게임서버와의 연결은 나중에 필요하면 추가하자.

 

CAuthSession.h

class CAuthSession : public PacketSession
{
public:
private:
	virtual void	OnWrite(int len) override;
	virtual void	OnReadPacket(BYTE* buffer, int len) override;
};

 

이제 OnReadPacket에서 구현해야 할 부분은 버퍼를 패킷으로 변환하고, 그것을 JobQueue에 넣는것이다.

그런데 버퍼 -> 패킷을 하는건 세션이 하는것보다는 전용 도구를 만드는게 맞는 것 같아 우선 패킷세션은 여기까지 하자.

 

다음엔 read버퍼 -> 패킷, 패킷 -> write버퍼를 담당할 ClientPacketHandler를 만들어보자.

 

 

 

'프로젝트 > Project_Island' 카테고리의 다른 글

9. DBConnectionPool 기초다지기  (0) 2026.03.05
8. PacketHandler  (0) 2026.03.04
6. 멀티스레드 작업  (0) 2026.03.03
5. WriteBuffer와 Send작업  (0) 2026.03.02
4. epoll기반 서버 기초다지기  (0) 2026.03.01

+ Recent posts