게임 서버 쪽을 처음 맡아서 C++ 라이브러리 가이드를 읽었다. 로그인 절차를 설명하는 부분에서 서버가 셋 나왔다.
Gateway
Master
Channel
로그인 한 번에 셋이 다 관여한다. 왜 한 군데서 안 하는지가 첫 의문이었다.
Table of contents
Open Table of contents
절차가 여섯 단계였다
가이드에 최초 로그인 절차가 적혀 있었다.
1) 게이트웨이가 인증 진행중인 세션을 등록
2) 게이트웨이가 마스터에 로그인 요청
3) 마스터에서 성공 응답이 오면
4) 게이트웨이가 채널에 세션 등록 요청
5) 채널에서 성공 응답이 오면
6) 클라이언트에 성공 결과 전달
게이트웨이가 두 곳에 각각 물어본다. 한 사람이 접속하는 데 여섯 단계를 거치는 셈이다.
역할이 달랐다
가이드에 각 서버의 역할도 있었다.
Gateway 클라이언트를 인증 처리한다
인증된 클라이언트의 패킷을 채널로 전송한다
Master 중복 로그인 여부를 체크한다
Channel 인증된 클라이언트가 전송한 패킷을 처리한다
읽고 나니 나뉜 이유가 짐작됐다.
Gateway 바깥과 붙는 곳 — 접속·인증
Master 전체를 아는 곳 — 중복 판정
Channel 게임 로직 — 실제 처리
Socket 으로 TCP 연결을 받는 곳과 이미 접속 중인지를 판단하는 곳과 실제 게임 처리를 하는 곳이다.
원인 — 중복 판정이 Master인 이유
중복 로그인을 판정하려면 이 유저가 다른 데 접속해 있는지를 알아야 한다. 접속을 받는 서버가 그것도 확인하면 될 것 같았는데 그렇지 않았다.
게이트웨이가 여럿이면 각자 자기 프로세스 안의 session_id 목록만 들고 있다. 다른 게이트웨이에 붙어 있으면 모른다. 한 곳에 모아 두지 않으면 판정이 안 되고 그것이 Master 다.
중복일 때의 절차는 더 복잡했다.
1) 게이트웨이에 동일 세션이 있으면 중복 세션을 찾아서 Kick
2) 게이트웨이가 새 세션을 생성하고 등록
3) 게이트웨이가 마스터에 로그인 요청
→ 마스터는 기존 세션을 갱신 처리 후 중복로그인 응답
4) 게이트웨이가 채널에 로그인 요청
→ 채널은 기존 세션을 종료 처리 후 새 세션을 만들어 중복로그인 응답
5) 클라이언트에 인증 결과 전달
세 곳에서 각각 기존 세션을 처리한다.
서버마다 들고 있는 것이 다르다
같은 기존 세션 정리인데 세 곳이 다르게 한다.
Gateway 찾아서 Kick — 연결을 끊는다
Master 갱신 처리 — 새 정보로 덮는다
Channel 종료 처리 후 새로 생성 — 지우고 만든다
처음엔 왜 다른지 몰랐는데 각자 들고 있는 것이 다르다는 것을 생각하니 이해가 됐다.
Gateway 실제 소켓 연결
Master 세션 정보 (누가 어디 있나)
Channel 게임 상태
연결은 끊어야 하고 정보는 덮으면 되고 게임 상태는 새로 시작해야 한다. 게이트웨이는 closesocket 으로 끊고 session_id 를 목록에서 빼면 되고 채널은 그 사람의 상태를 DB 에 쓰고 메모리에서 내려야 한다.
같은 정리라고 한 곳에서 하려고 하면 다른 서버가 들고 있는 것을 모른다.
실패 처리와 로그아웃 순서
예외 처리 항목이 따로 있었다.
게이트웨이는 마스터 세션 등록 실패 시 클라이언트를 Kick 한다
게이트웨이는 채널 세션 등록 실패 시 클라이언트를 Kick 한다
마스터는 동일 세션이 있으면 기존 세션 정보를 갱신 처리 후
중복 로그인 세션을 Kick 하라고 요청한다
앞의 둘이 같은 모양이라 어느 한 곳이라도 실패하면 전체 실패로 보고 Kick 한다. 처음에는 나머지 둘이 성공했으니 살려 두는 것이 나을 것 같았다.
그런데 마스터 등록은 됐는데 채널 등록이 실패하면 마스터에는 있고 채널에는 없는 유저가 남는다. 나중에 그 유저가 무엇을 하려고 하면 채널이 모르는 유저가 된다. 반쯤 등록된 상태가 아예 실패한 것보다 나쁘다. 서버 사이에는 DB 트랜잭션처럼 한 번에 물리는 장치가 없다. 그래서 실패를 전체 실패로 보는 것이 가장 단순했다.
로그아웃 쪽도 봤다.
인증된 클라이언트
1) 게이트웨이가 마스터로 로그오프 요청
2) 게이트웨이가 채널로 로그오프 요청
3) 게이트웨이에 등록된 세션 삭제
인증 안 된 클라이언트
1) 세션을 삭제한다
인증 여부에 따라 갈린다. 인증 전이면 마스터와 채널에 아직 없으니 게이트웨이만 정리하면 되고 등록한 곳만 정리한다.
로그인 게이트웨이 등록 → 마스터 → 채널
로그아웃 마스터 → 채널 → 게이트웨이 삭제
게이트웨이 세션이 가장 먼저 생기고 가장 나중에 지워진다. 바깥과 붙어 있는 곳이라 그것이 살아 있어야 나머지에 요청을 보낼 수 있다.
패킷 정의서를 봤더니 응답 형식도 그랬다.
result_code 0: 실패, 1: 성공
result_data
kick_reson 실패코드 (실패했을 경우에만 값이 있다)
result_code 는 항상 있고 사유는 실패했을 때만 있다. 읽는 쪽이 먼저 성공 여부를 보고 실패면 사유를 읽는 구조이고 나눠 두면 성공 처리 쪽에서 사유를 신경 안 써도 된다.
가이드를 읽어도 안 나온 것이 있다.
게이트웨이가 여러 대일 때 마스터가 어떻게 다 아는가
채널이 여러 대면 어느 채널로 가는가
마스터가 죽으면 어떻게 되는가
한 대일 때의 절차만 적혀 있어서 실제로 여러 대를 띄우는 상황을 보면 알게 될 것 같다.
정리
- C++ 라이브러리의 로그인 하나에 서버가 셋 관여한다
- 접속·중복판정·게임로직으로 역할이 갈린다
Socket을 받는 서버는 자기 프로세스 안의 목록만 알아 중복 판단을 못 한다- 전체
session_id를 아는 곳이 하나 필요해서 판정이 따로 있다 - 같은 기존 세션 정리도 서버마다 다르게 한다 — 들고 있는 것이 다르다
- 한쪽은
closesocket이고 다른 쪽은 상태를 DB 에 쓰는 것이다 - 서버 사이에는 DB 트랜잭션 같은 장치가 없다
- 그래서 어느 한 곳이라도 실패하면 전체를 실패로 보고 Kick 한다
- 반쯤 등록된 상태가 실패보다 나쁘다
- 로그인과 로그아웃의 순서가 반대다
- 인증 여부에 따라 정리 범위가 갈린다
- 응답에서 성공 여부와 실패 사유가 분리돼 있다