
이번 글에서는 프로젝트의 전반적인 구조에 대해 살펴보도록 하겠습니다.
이 글에서는 코드를 필요한 부분만 가져와 설명하거나, 설명을 위해 배치를 임의로 변경하기도 합니다.
원본 코드는 https://github.com/pagefaultgames/rogueserver.git 에서 확인하실 수 있습니다.
포켓로그의 서버는 rogueserver.go라는 main파일, 그리고 api, db, defs라는 3개의 패키지가 존재하는 단순한 구성입니다.

package main
func main() {
// env stuff
proto := getEnv("proto", "tcp")
addr := getEnv("addr", "0.0.0.0:8001")
...
// get database connection
err := db.Init(dbuser, dbpass, dbproto, dbaddr, dbname)
if err != nil {
log.Fatalf("failed to initialize database: %s", err)
}
// create listener
listener, err := createListener(proto, addr)
if err != nil {
log.Fatalf("failed to create net listener: %s", err)
}
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)
}
// 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)
})
}
http.Serve(listener, handler)를 통해 서버를 실행합니다. 구조체 타입들이 정의되어 있습니다.
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
...
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)
}
...
그 외 db패키지에 있는 파일들은 각자 역할에 맞는 쿼리들을 정의하고 있습니다. account.go에는 계정과 관련된 쿼리, daily.go에는 데일리런과 관련된 쿼리, game.go에는 게임 통계정보와 관련된 쿼리, savedata.go에는 세이브 데이터와 관련된 쿼리가 정의되어 있습니다. 이들은 모두 db.go에 있는 handle변수를 활용합니다.
func handleAccountInfo(w http.ResponseWriter, r *http.Request) {}
func handleAccountRegister(w http.ResponseWriter, r *http.Request) {}
...
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)
...
}
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에서 핸들러를 정의함에 있어 필요한 구체적인 기능들을 정의하고 있습니다.