지금까지는 클라이언트가 그냥 텍스트 메시지를 보내면 서버는 그걸 그대로 돌려주는것만 해봤는데, 이제 본격적으로 프로토버프를 적용시켜 패킷을 주고받을 수 있게 만들어보자.
먼저 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 를 성공한 뒤에 해야 하는 일은 이렇다.
- 메인 스레드에서 ReadBuffer가 어떤 패킷인지 확인 -> header 추가 필요
- ReadBuffer 중 패킷 부분을 패킷 클래스 객체로 변환 -> 버퍼를 받아서 패킷 클래스 객체를 리턴하는 함수 (ParsePacket)
- 객체를 잡큐에 넣고, 워커스레드가 잡큐에서 패킷 클래스 객체를 받음.
- 워커스레드는 패킷(ex. C_Login)에 해당하는 DB작업을 한다. -> 패킷에 따라 다른 일 해주는 함수 (HandlePacket)
- DB작업 결과를 패킷 클래스 객체(ex. S_Login)에 담고, 이를 직렬화 함 -> 패킷을 WriteBuffer로 만들 함수
- WriteBuffer를 Session의 writeBufferQueue에 담으면서 epoll이 세션의 write이벤트를 감지할 수 있게 설정.
- 메인스레드의 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 |