I'm FanJae.

Unity 게임 개발 캠프 개인 프로젝트 9일차. MySQL 연동과 로그인 서버 DB 조회 구조 추가 본문

Projects/MyToyMapleServer

Unity 게임 개발 캠프 개인 프로젝트 9일차. MySQL 연동과 로그인 서버 DB 조회 구조 추가

FanJae 2026. 9. 22. 23:46

1. 시작에 앞서

- 이전 작업까지 LoginServer와 GameServer를 분리하고, 로그인 이후 캐릭터를 선택한 뒤 인증 티켓을 이용해 GameServer로 이동하는 흐름을 구현했다.

- 다만 아직 로그인 계정과 캐릭터 정보는 실제 데이터베이스에서 가져오는 것이 아니라 테스트를 위한 임시 데이터를 사용하고 있었다.

- 예를 들어 특정 계정만 로그인에 성공하도록 처리하거나, 캐릭터 목록 역시 코드 내부에 직접 작성해두는 방식이었다.

LoginServer

계정 확인
→ 임시 데이터 사용

캐릭터 목록
→ 코드 내부에 직접 작성된 데이터 사용

캐릭터 선택
→ 임시 캐릭터 목록을 기준으로 확인

- 서버 간 인증 흐름을 확인하는 단계에서는 이 정도로도 충분했지만, 실제 서버 구조로 확장하기 위해서는 계정과 캐릭터 데이터를 별도로 관리할 필요가 있었다.

- 따라서 이번 작업에서는 MySQL을 이용할 수 있는 개발 환경과 초기 데이터 구조를 만들고, LoginServer가 DB에서 필요한 데이터를 조회하도록 구조를 확장했다.

- 추가로 로그인부터 캐릭터 선택까지 전체 과정이 정상적으로 동작하는지 확인하기 위한 통합 테스트 클라이언트도 추가했다.


2. 기존 임시 데이터 구조

- 기존 LoginServer에서는 실제 DB를 사용하지 않고 테스트용 데이터를 기준으로 로그인과 캐릭터 선택을 처리했다.

- 전체적인 흐름은 다음과 같았다.

LoginRequest
    ↓
코드 내부의 임시 계정 확인
    ↓
LoginResponse

- 캐릭터 목록도 비슷했다.

CharacterListRequest
    ↓
LoginServer 내부 임시 CharacterInfo 조회
    ↓
CharacterListResponse

- 이 방식은 네트워크 패킷이나 인증 티켓 구조를 먼저 구현하기에는 단순하지만, 계정이나 캐릭터가 늘어날 때마다 서버 코드를 직접 수정해야 한다.

새로운 계정 추가
     ↓
LoginServer 코드 수정

새로운 캐릭터 추가
     ↓
LoginServer 코드 수정

- 결국 실제 게임 데이터를 관리하는 역할과 로그인 처리 코드가 서로 섞이게 된다.

- 따라서 네트워크 흐름을 어느 정도 구성한 이후에는 실제 데이터의 저장 위치를 코드 외부로 분리할 필요가 있었다.


3. MySQL 개발 환경 추가

- 먼저 서버에서 사용할 데이터를 관리하기 위해 MySQL 기반의 개발 환경을 추가했다.

- 이 단계에서 중요한 점은 단순히 DB 프로그램을 사용하는 것보다, 서버가 필요로 하는 데이터 구조를 먼저 정의하는 것이었다.

- 지금까지 LoginServer에서 사용하고 있던 데이터를 생각하면 최소한 다음과 같은 관계가 필요하다.

Account
   │
   └── Character

- 하나의 계정은 여러 캐릭터를 가질 수 있고, 로그인 이후에는 인증된 계정을 기준으로 캐릭터 목록을 조회해야 한다.

Account
   │
   ├─ Character A
   ├─ Character B
   └─ Character C

- 따라서 이번 작업에서는 MySQL을 사용할 수 있는 개발 환경과 함께 로그인 및 캐릭터 조회에 필요한 초기 스키마를 추가했다.

- 이전까지 코드 내부에 존재하던 임시 데이터를 DB에서 관리할 수 있는 기반을 만든 것이다.


4. 서버 코드와 데이터 분리

- DB를 추가하면서 가장 먼저 달라지는 부분은 LoginServer가 데이터를 얻는 방식이다.

- 기존에는 LoginServer 내부에 필요한 값이 존재했다.

LoginServer
    │
    ├─ 임시 Account 정보
    └─ 임시 Character 정보

- DB를 사용하면 다음과 같이 역할을 나눌 수 있다.

LoginServer
    │
    │ 데이터 조회
    ▼
Database
    │
    ├─ Account
    └─ Character

- LoginServer는 로그인과 캐릭터 선택이라는 게임의 흐름을 처리하고, 실제 계정이나 캐릭터 데이터는 Database에서 관리하는 형태가 된다.

- 이 구조로 변경하면 새로운 계정이나 캐릭터가 추가되더라도 LoginServer의 패킷 처리 코드를 직접 수정할 필요가 줄어든다.


5. LoginServer의 DB 조회 구조 추가

- DB 환경을 만든 뒤 LoginServer에서 실제 데이터를 조회할 수 있는 구조를 추가했다.

- 전체적인 목적은 기존의 임시 데이터 접근을 다음과 같이 변경하는 것이었다.

기존

LoginPacketHandler
      ↓
임시 데이터 확인
변경

LoginPacketHandler
      ↓
DB 조회 구조
      ↓
MySQL

- 패킷 처리 코드에서 직접 SQL과 연결 로직까지 모두 처리하기보다는 DB에서 필요한 데이터를 조회하는 역할을 분리해서 사용할 수 있도록 구조를 확장하는 방향으로 진행했다.

- 이를 통해 LoginServer에서는 결과적으로 아래와 같은 흐름으로 처리가 가능하다.

요청 수신
   ↓
필요한 데이터 조회
   ↓
조회 결과를 기준으로 검증
   ↓
응답

 


6. 로그인 처리와 DB 조회

- 가장 먼저 DB를 적용할 수 있는 부분은 로그인이다.

- 기존에는 특정 계정 ID인지 코드에서 직접 확인했다.

LoginRequest
    ↓
accountId 직접 비교
    ↓
Success / InvalidAccount

- DB를 사용하면 로그인 검증의 기준이 코드에 작성된 하드코딩 된 값이 아니라 실제 저장된 계정 데이터가 된다.

LoginRequest
    ↓
Account 조회
    ↓
 ┌─────────────┐
 │             │
존재          없음
 │             │
 ↓             ↓
로그인 성공   로그인 실패

- 로그인 성공 이후에는 기존과 동일하게 LoginSession에 인증된 계정 정보를 유지한다.

DB 조회 성공
     ↓
LoginSession
     │
     ├─ authenticated
     └─ accountId

 


7. 캐릭터 목록 조회와 DB

- 캐릭터 목록 역시 동일한 문제를 가지고 있었다.

- 기존에는 LoginServer 내부에 테스트용 캐릭터 목록을 가지고 있었다.

characters
 ├─ Warrior
 └─ Magician

- 따라서 로그인한 사용자가 누구인지와 관계없이 코드에 작성된 데이터를 기준으로 처리하게 된다.

- 실제 구조에서는 현재 로그인된 accountId를 기준으로 해당 계정이 소유한 캐릭터를 조회해야 한다.

LoginSession
   │
   │ accountId
   ▼
Database
   │
   │ 해당 계정의 Character 조회
   ▼
CharacterListResponse

- 이 구조를 사용하면 계정마다 서로 다른 캐릭터 목록을 반환할 수 있다.

Account 1
 ├─ Warrior
 └─ Magician

Account 2
 └─ Archer

- 캐릭터 목록을 LoginServer 내부의 고정 데이터가 아니라 실제 계정 데이터와 연결할 수 있게 되는 것이다.


8. 캐릭터 선택 검증과 DB

- 캐릭터 선택에서도 DB 조회가 필요하다.

- 클라이언트가 characterId를 보내더라도 해당 캐릭터가 실제로 현재 로그인한 계정의 캐릭터인지 확인해야 한다.

CharacterSelectRequest
        │
        │ characterId
        ▼
LoginServer
        │
        │ accountId + characterId
        ▼
Database

- 단순히 characterId가 존재하는지만 검사해서는 부족하다.

- 예를 들어 다음과 같은 데이터가 있다고 가정한다.

Account 1
 └─ Character 1001

Account 2
 └─ Character 2001

- Account 1로 로그인한 사용자가 2001을 전달했다면 해당 요청은 허용하면 안 된다.

- 따라서 캐릭터 선택에서는 다음 관계를 확인해야 한다.

현재 LoginSession의 accountId
             +
클라이언트가 요청한 characterId
             ↓
해당 계정이 소유한 캐릭터인지 확인

- 이후 정상적인 캐릭터인 경우에만 이전에 구현했던 인증 티켓 발급 과정으로 넘어간다.

캐릭터 소유 확인
      ↓
AuthKey 생성
      ↓
AuthTicket 생성
      ↓
GameServer 등록

9. 기존 인증 티켓 구조와 연결

- DB 조회를 추가하더라도 이전에 구현한 GameServer 입장 과정 자체는 크게 변경할 필요가 없다.

- 달라지는 것은 인증 티켓을 생성하기 전에 사용하는 데이터 정보가 담기는 위치가 달라진다.

- 기존에는 임시 Account와 Character였다면, Database에 저장되는 형태다.

- GameServer 입장에서 보면 최종적으로 전달받는 인증 티켓은 여전히 다음 정보를 가지고 있다.

AuthTicket
 ├─ accountId
 ├─ characterId
 └─ authKey

- 즉, LoginServer의 데이터 조회 부분을 변경하더라도 LoginServer와 GameServer 사이에 만들어둔 인증 티켓 구조를 그대로 활용할 수 있다.

- 앞에서 서버 사이의 역할을 분리해둔 부분이 여기서도 그대로 유지되는 형태다.


10. 통합 테스트 클라이언트 추가

- DB 조회 구조까지 추가하면서 로그인 과정 전체를 한 번에 확인할 필요가 있었다.

- 개별 함수 단위로 데이터가 조회되는지만 확인하는 것과 실제 클라이언트 흐름이 정상적으로 동작하는지는 별개의 문제다.

- 실제 로그인 흐름은 여러 패킷과 서버 상태가 이어져 있다.

LoginRequest
    ↓
LoginResponse
    ↓
CharacterListRequest
    ↓
CharacterListResponse
    ↓
CharacterSelectRequest
    ↓
CharacterSelectResponse

- 또한 캐릭터 선택 과정에서는 LoginServer 내부 처리만 끝나는 것이 아니라 GameServer에 인증 티켓을 등록하는 과정까지 포함된다.

CharacterSelectRequest
        ↓
LoginServer
        ↓
DB에서 캐릭터 확인
        ↓
AuthKey 생성
        ↓
GameServer에 AuthTicket 등록
        ↓
CharacterSelectResponse

- 따라서 이번 작업에서는 LoginServer의 전체 흐름을 실제 패킷 단위로 확인할 수 있는 통합 테스트 클라이언트를 추가했다.


11. 통합 테스트가 필요한 이유

- 지금까지 기능을 하나씩 추가하면서 각각의 코드가 정상적으로 동작하더라도 전체 흐름에서는 다른 문제가 발생할 수 있다.

- 예를 들어 다음과 같은 부분을 같이 확인해야 한다.

1. DB 연결 성공 여부
2. 로그인 데이터 조회
3. LoginSession 인증 상태 저장
4. 캐릭터 목록 조회
5. 캐릭터 소유 여부 확인
6. AuthKey 생성
7. GameServer 티켓 등록
8. 패킷 응답 순서

- 특히 LoginServer와 GameServer가 분리되어 있기 때문에 한쪽 기능만 정상이라고 해서 전체 로그인 과정이 정상이라고 볼 수 없다.

- 통합 테스트 클라이언트를 이용하면 실제 클라이언트와 비슷하게 패킷을 순서대로 전달하면서 이 흐름을 확인할 수 있다.

[Test Client]
      ↓
LoginServer
      ↓
Database
      +
LoginServer
      ↓
GameServer

- 네트워크, DB, 세션, 인증 티켓이 연결된 전체 과정 자체를 테스트 대상으로 만든 것이다.


12. 전체 구조 변화

- 이번 작업 이전의 로그인 서버는 다음과 같은 구조에 가까웠다.

Client
   ↓
LoginServer
   │
   ├─ 임시 Account 데이터
   └─ 임시 Character 데이터
   │
   ↓
GameServer

- DB 조회 구조를 추가하면서 다음과 같이 변경된다.

              MySQL
                ▲
                │
                │ Account / Character 조회
                │
Client ───▶ LoginServer
                │
                │ AuthTicket 등록
                ▼
            GameServer

- 각각의 역할도 조금 더 명확해진다.

Database
→ 실제 계정 / 캐릭터 데이터 관리

LoginServer
→ 로그인, 캐릭터 조회 및 선택
→ 게임 서버 이동을 위한 인증 정보 발급

GameServer
→ 전달받은 인증 티켓 검증
→ 실제 게임 세션 생성

13. 변경하면서 얻은 점

- 처음 로그인 서버를 구현할 때는 네트워크 흐름을 먼저 확인하기 위해 코드 내부의 임시 데이터를 사용했다.

- 이 방식은 빠르게 구조를 확인하기에는 적합했다.

임시 데이터
    ↓
빠르게 Login 흐름 확인

- 이후 캐릭터 선택과 GameServer 입장까지 연결하면서 실제 데이터 관리 구조가 필요해졌다.

- 따라서 이번 단계에서 MySQL을 추가하고 로그인 서버의 데이터 조회를 DB 기반으로 옮기기 시작했다.

하드코딩 데이터
      ↓
Database 데이터

- 이 과정에서 기존 흐름은 최대한 유지하면서

 데이터를 가져오는 부분만 실제 DB 조회 구조로 교체할 수 있었다.


14. 고려해 볼 만한 사항

- 현재 DB 조회 구조를 추가했다고 해서 데이터베이스 처리가 모두 끝난 것은 아니다.

- 서버에서 DB를 사용하기 시작하면 추가로 생각해야 할 부분이 많아진다.

- 우선 DB 요청은 네트워크 I/O이기 때문에 요청 시간이 항상 일정하지 않다.

LoginServer
    ↓
DB Query
    ↓
응답 대기

- 현재 LoginServer의 네트워크 처리는 IOCP 기반으로 구성되어 있으므로, 이후 DB 조회까지 많아진다면 네트워크 이벤트 처리와 DB 작업을 어떤 방식으로 분리할 것인지도 고려할 필요가 있다.

- 또한 연결을 요청할 때마다 새로운 DB 연결을 생성하는 방식이라면 연결 생성 비용 역시 문제가 될 수 있다.

- 따라서 이후에는 다음과 같은 부분도 고려할 수 있다.

1. DB Connection 관리
2. Connection Pool
3. 비동기 DB 처리
4. DB 작업을 처리할 별도 Thread
5. Transaction
6. Query 실패 처리
7. Reconnect

- 캐릭터 데이터가 많아진다면 모든 데이터를 한 번에 가져올 것인지, 필요한 데이터만 조회할 것인지도 결정해야 한다.

- 이번 작업에서는 우선 코드 내부에 존재하던 임시 데이터를 실제 Database에서 조회할 수 있도록 LoginServer에 DB 계층을 연결하는 데 중점을 두었다.


15. 정리

- 기존 LoginServer에서는 계정과 캐릭터 정보를 코드 내부의 임시 데이터를 이용해 처리하고 있었다.

- 서버 간 로그인 및 인증 티켓 흐름을 구현하는 단계에서는 충분했지만, 실제 데이터가 늘어나면 서버 코드를 직접 수정해야 하는 구조였다.

- 이번 작업에서는 먼저 MySQL을 사용할 수 있는 개발 환경과 로그인 및 캐릭터 데이터에 필요한 초기 스키마를 추가했다.

- 이후 LoginServer가 코드 내부의 임시 데이터가 아니라 DB에서 계정과 캐릭터 정보를 조회할 수 있도록 데이터 조회 구조를 추가했다.

- 이를 통해 로그인, 캐릭터 목록 조회, 캐릭터 선택에서 실제 저장 데이터를 사용할 수 있는 기반을 만들었다.

- 기존에 구현했던 인증 티켓 구조는 그대로 유지하여, DB에서 확인된 계정과 캐릭터 정보를 기반으로 GameServer 입장용 인증 티켓을 생성하는 흐름으로 연결할 수 있게 되었다.

- 마지막으로 로그인부터 캐릭터 선택까지의 전체 패킷 흐름을 확인하기 위해 LoginServer 통합 테스트 클라이언트를 추가했다.

Comments