pokerogue 게임 서버 코드 분석하기 (2) - 전체 구조 파악

최창효·2025년 7월 19일
post-thumbnail

프로젝트 구조 분석하기

이번 글에서는 프로젝트의 전반적인 구조에 대해 살펴보도록 하겠습니다.

이 글에서는 코드를 필요한 부분만 가져와 설명하거나, 설명을 위해 배치를 임의로 변경하기도 합니다.
원본 코드는 https://github.com/pagefaultgames/rogueserver.git 에서 확인하실 수 있습니다.

포켓로그의 서버는 rogueserver.go라는 main파일, 그리고 api, db, defs라는 3개의 패키지가 존재하는 단순한 구성입니다.


rogueserver.go

package main

func main() {
	// env stuff
    proto := getEnv("proto", "tcp")
	addr := getEnv("addr", "0.0.0.0:8001")
	...
  • main 패키지이며 main함수를 정의하고 있습니다.
  • 변수값을 세팅합니다. 환경변수에 지정된 값이 있다면 그 값을 사용하고, 없다면 초기값을 활용합니다.
	// get database connection
	err := db.Init(dbuser, dbpass, dbproto, dbaddr, dbname)
	if err != nil {
		log.Fatalf("failed to initialize database: %s", err)
	}
  • db에 정의해둔 초기화 함수를 호출합니다.
	// create listener
	listener, err := createListener(proto, addr)
	if err != nil {
		log.Fatalf("failed to create net listener: %s", err)
	}
  • Listener를 생성합니다. Listener는 네트워크 연결을 받아들이는 객체입니다. proto변수는 TCP, addr변수는 0.0.0.0:8001을 기본값으로 사용하기 때문에 현재 Listener는 0.0.0.0:8001으로 들어오는 TCP요청을 받게 됩니다.
	mux := http.NewServeMux()

	// init api
	if err := api.Init(mux); err != nil {
		log.Fatal(err)
	}
  • mux를 생성하고 api에 정의해둔 초기화 함수를 호출합니다. mux는 들어오는 요청 URL을 등록된 패턴 목록과 비교하여 적절한 핸들러를 호출하는 역할을 담당합니다.
	// start web server
	handler := prodHandler(mux, gameurl)
	if debug {
		handler = debugHandler(mux)
	}

	if tlscert == "" {
		err = http.Serve(listener, handler)
	} else {
		err = http.ServeTLS(listener, handler, tlscert, tlskey)
	}
}

func prodHandler(router *http.ServeMux, clienturl string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
		w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST")
		w.Header().Set("Access-Control-Allow-Origin", clienturl)

		router.ServeHTTP(w, r)
	})
}
  • 서버를 실행합니다. prodHandler는 CORS설정을 추가한 뒤 핸들러를 반환합니다.
  • http.Serve(listener, handler)를 통해 서버를 실행합니다.

defs

구조체 타입들이 정의되어 있습니다.

type SystemSaveData struct {
	TrainerId          int                `json:"trainerId"`
	SecretId           int                `json:"secretId"`
	Gender             int                `json:"gender"`
	DexData            DexData            `json:"dexData"`
	StarterData        StarterData        `json:"starterData"`
	StarterMoveData    StarterMoveData    `json:"starterMoveData"`    // Legacy
	StarterEggMoveData StarterEggMoveData `json:"starterEggMoveData"` // Legacy
	GameStats          GameStats          `json:"gameStats"`
	Unlocks            Unlocks            `json:"unlocks"`
	AchvUnlocks        AchvUnlocks        `json:"achvUnlocks"`
	VoucherUnlocks     VoucherUnlocks     `json:"voucherUnlocks"`
	VoucherCounts      VoucherCounts      `json:"voucherCounts"`
	Eggs               []EggData          `json:"eggs"`
	EggPity            []int              `json:"eggPity"`
	UnlockPity         []int              `json:"unlockPity"`
	GameVersion        string             `json:"gameVersion"`
	Timestamp          int                `json:"timestamp"`
}

type DexData map[int]DexEntry

...

db

db.go

var handle *sql.DB

func Init(username, password, protocol, address, database string) error {
	var err error

	handle, err = sql.Open("mysql", username+":"+password+"@"+protocol+"("+address+")/"+database)
	if err != nil {
		return fmt.Errorf("failed to open database connection: %s", err)
	}
    
    ...
  • sql.Open()을 통해 데이터베이스 연결을 관리할 수 있는 핸들러를 생성합니다.

그 외 db패키지에 있는 파일들은 각자 역할에 맞는 쿼리들을 정의하고 있습니다. account.go에는 계정과 관련된 쿼리, daily.go에는 데일리런과 관련된 쿼리, game.go에는 게임 통계정보와 관련된 쿼리, savedata.go에는 세이브 데이터와 관련된 쿼리가 정의되어 있습니다. 이들은 모두 db.go에 있는 handle변수를 활용합니다.


api

endpoints.go

func handleAccountInfo(w http.ResponseWriter, r *http.Request) {}
func handleAccountRegister(w http.ResponseWriter, r *http.Request) {}
...
  • 핸들러 함수를 정의하고 있습니다.

common.go

func Init(mux *http.ServeMux) error {
	// account
	mux.HandleFunc("GET /account/info", handleAccountInfo)
	mux.HandleFunc("POST /account/register", handleAccountRegister)
	mux.HandleFunc("POST /account/login", handleAccountLogin)
	mux.HandleFunc("POST /account/changepw", handleAccountChangePW)
	mux.HandleFunc("GET /account/logout", handleAccountLogout)

	// game
	mux.HandleFunc("GET /game/titlestats", handleGameTitleStats)
	mux.HandleFunc("GET /game/classicsessioncount", handleGameClassicSessionCount)
	...
}
  • URL과 핸들러를 매핑하는 역할을 담당합니다. 이 핸들러는 endpoints.go에 정의된 핸들러입니다.

stats.go

var (
	scheduler           = cron.New(cron.WithLocation(time.UTC))
	playerCount         int
	battleCount         int
	classicSessionCount int
)

func scheduleStatRefresh() error {
	...
}

func updateStats() error {
	...
}

포켓로그 메인페이지에는 몇명의 플레이어가 플레이중인지를 나타내는 정보가 표시됩니다. stats.go는 이러한 정보를 주기적으로 갱신하는데 필요한 변수와 기능들이 정의되어 있습니다.

그 외 account, daily, savedata 패키지에 정의된 파일들은 endpoints.go에서 핸들러를 정의함에 있어 필요한 구체적인 기능들을 정의하고 있습니다.


정리

  • api 패키지는 endpoints.go 파일에서 핸들러를 정의하고, common.go파일에서 핸들러와 URL을 매핑하고 있습니다.
  • db 패키지는 데이터베이스 커넥션 및 쿼리와 관련된 내용이 정의되어 있습니다.
  • rogueserver.go라는 main 파일에서는 api와 db의 초기화(Init)를 직접 호출하며 서버를 실행시킵니다.
profile
기록하고 정리하는 걸 좋아하는 백엔드 개발자입니다.

0개의 댓글