GS2-Gateway
GS2-Gateway는 게임 클라이언트와 서버 사이에서 WebSocket을 통한 상시 접속을 유지하고, 서버가 임의의 시점에 알림을 보낼 수 있도록 하는 기능을 제공합니다.
일반적인 게임 서버와의 통신은 클라이언트의 요청에 서버가 응답하는 형태이지만, GS2-Gateway를 사용하면 서버가 시작하는 알림(메시지 수신, 친구 신청, 길드로부터의 소집 등)을 실시간으로 클라이언트에 전달할 수 있습니다.
sequenceDiagram participant Client participant Gateway as GS2-Gateway participant Service as 각 마이크로서비스 Client->>Gateway: WebSocket 접속 Client->>Gateway: SetUserId(인증) Service->>Gateway: SendNotification Gateway->>Client: 알림 페이로드 전달
주요 기능
WebSocket 상시 접속
GS2-Gateway는 WebSocket 프로토콜로 클라이언트로부터의 접속을 받아들이고, 사용자별로 접속 정보를 보유합니다. 다른 마이크로서비스로부터 “이 사용자에게 알림을 보내고 싶다"는 요청이 오면, 접속 중인 클라이언트에 페이로드를 전달합니다.
주요 연계 대상은 다음과 같습니다.
- GS2-Inbox: 신규 메시지 수신 알림
- GS2-Friend: 친구 신청·승인 알림
- GS2-Guild: 길드 참가 신청, 길드 내 상황 변화 알림
- GS2-Matchmaking: 매칭 완료 알림
- GS2-Distributor: 트랜잭션 자동 실행을 위한 알림
- GS2-JobQueue: 신규 작업이 쌓였을 때의 알림
동시 접속 제어
네임스페이스 내의 사용자는 원칙적으로 동시에 하나의 세션만 보유할 수 있습니다.
SetUserId 실행 시 allowConcurrentAccess를 false로 설정하면, 이미 다른 세션이 접속 중인 경우 새로운 세션 측에서 동시 접속 오류로 감지할 수 있습니다.
true로 설정하면, 새로운 세션이 접속된 시점에 기존 세션이 끊어집니다.
이 기능을 이용하면 여러 기기에서의 동시 로그인을 억제할 수 있습니다. GS2-Account의 비밀번호 자동 변경 기능과 조합하면, 계정 공유나 인수인계 후 이전 기기의 접속 차단을 더욱 강력하게 수행할 수 있습니다.
Firebase Cloud Messaging 연계 (모바일 푸시 알림)
클라이언트가 실행되지 않은 (WebSocket이 접속되지 않은) 상태에서도 알림을 전달하고 싶은 경우, Firebase Cloud Messaging (FCM)과의 연계 기능을 이용할 수 있습니다.
GS2-Gateway는 FCM HTTP v1 API로 알림을 전송합니다.
전송에는 GS2가 리전별로 보유하고 있는 Google 서비스 계정을 사용하므로, FCM의 서버 키(기존 firebaseSecret)를 GS2에 등록할 필요가 없습니다.
기존 FCM 서버 키를 사용한 전송은 Google 측에서 제공이 종료되었으며, firebaseSecret은 더 이상 사용되지 않습니다.
연계에는 다음 설정이 필요합니다.
- 네임스페이스의
firebaseProjectId에 알림의 전송원이 되는 Firebase 프로젝트의 프로젝트ID를 설정합니다. - 해당 Firebase (Google Cloud) 프로젝트에서 Firebase Cloud Messaging API를 활성화합니다.
- 같은 프로젝트의 IAM에서, 이용 중인 리전에 대응하는 GS2의 서비스 계정에 「Firebase Cloud Messaging API 관리자」(
roles/firebasemessaging.admin) 역할을 부여합니다. - 각 사용자의 기기에서 취득한 FCM 디바이스 토큰을
setFirebaseToken으로 GS2-Gateway에 등록합니다.
권한을 부여할 서비스 계정은 이용 중인 리전별로 다음과 같습니다.
| 리전 | 서비스 계정 |
|---|---|
| ap-northeast-1 | api-access@gs2-ap-northeast-1-live.iam.gserviceaccount.com |
| us-east-1 | api-access@gs2-us-east-1-live.iam.gserviceaccount.com |
| eu-west-1 | api-access@gs2-eu-west-1-live.iam.gserviceaccount.com |
| ap-southeast-1 | api-access@gs2-ap-southeast-1-live.iam.gserviceaccount.com |
이는 GS2-Money2의 구독 검증을 위해 Google Play Console에서 권한을 부여하는 서비스 계정과 동일한 것입니다.
GS2-Gateway는 알림 전달 시 WebSocket 세션이 존재하지 않는 사용자에 대해서는, 등록된 FCM 디바이스 토큰을 사용해 FCM을 통해 푸시 알림을 보냅니다.
푸시 알림의 제목에는 알림의 subject, 본문에는 payload가 설정되며, 필요에 따라 알림음을 지정할 수 있습니다.
issuer subject payload는 데이터 페이로드에도 포함되므로, 클라이언트 측에서 알림의 발생원에 따른 처리를 수행할 수 있습니다.
이를 통해 앱이 실행되지 않은 상태에서도 사용자에게 알림을 전달할 수 있습니다.
알림 엔트리의 enableTransferMobileNotification을 활성화하면, 알림별로 모바일 푸시로의 전달 여부를 제어할 수 있습니다.
디바이스 토큰이 등록되지 않은 사용자나 firebaseProjectId가 설정되지 않은 네임스페이스에서는, 알림의 전송 결과가 offline이 됩니다.
앱 삭제 등으로 인해 FCM으로부터 디바이스 토큰이 무효(미등록)라고 통지된 경우에는, 저장된 디바이스 토큰을 자동으로 삭제합니다.
다국어 푸시 메시지
setFirebaseToken에서는 디바이스 토큰과 함께 플레이어의 언어 설정을 locale로 등록할 수 있습니다.
locale은 생략 가능하며, ja나 en과 같은 임의의 문자열을 지정할 수 있습니다 (고정된 목록은 없습니다).
알림의 payload를 다음 형식의 JSON으로 만들어 두면, 등록된 locale에 따라 푸시 알림의 본문을 구분하여 보낼 수 있습니다.
{
"mobile": [
{"locale": "ja", "message": "こんにちは"},
{"locale": "en", "message": "Hello"},
{"locale": "default", "message": "Hello"}
]
}푸시 알림의 본문은 다음 순서로 선택됩니다.
locale이 디바이스 토큰에 등록된locale과 일치하는 엔트리- 찾지 못한 경우에는
locale이default인 엔트리 - 그것도 찾지 못한 경우에는 배열의 첫 번째 엔트리
payload를 JSON으로 해석할 수 없는 경우나, mobile이 존재하지 않거나・배열이 아니거나・빈 배열인 경우에는, payload를 그대로 푸시 알림의 본문으로 사용합니다.
message가 문자열이 아닌 엔트리는 무시됩니다.
푸시 알림의 제목은 이 경우에도 항상 알림의 subject입니다.
또한 데이터 페이로드에는 가공 전의 payload (및 issuer subject)가 그대로 포함되므로, 알림에서 앱을 실행했을 때 원래의 JSON을 파싱하여 이용할 수 있습니다.
GS2-Gateway를 경유하여 알림을 전송하는 서비스 (GS2-Chat의 게시물 알림, GS2-Friend의 친구 요청 알림, GS2-Matchmaking의 매칭 성사 알림 등)는 각각의 네임스페이스에 gatewayNamespaceId / enableTransferMobileNotification / sound와 같은 알림 설정을 가지고 있습니다.
이 설정에는 mobileNotificationMessages를 지정할 수 있습니다. mobileNotificationMessages는 { locale, title, message } 엔트리의 목록으로, locale은 32자 이내, title은 256자 이내 (생략 가능), message는 1024자 이내이며, 최대 100건까지 지정할 수 있고, enableTransferMobileNotification이 활성화되어 있으면 매니지먼트 콘솔에서 편집할 수 있습니다.
알림이 모바일 푸시로 전달될 때는 위와 동일한 규칙 (디바이스 토큰에 등록된 locale → default → 배열의 첫 번째)으로 엔트리가 선택되며, message가 푸시 알림의 본문, title이 푸시 알림의 제목이 됩니다 (title이 비어 있는 경우에는 알림의 subject, 예를 들어 Gs2Chat:Post가 사용됩니다). 정적인 텍스트만 지원하며, 플레이스홀더는 치환되지 않습니다.
다음은 GS2-Chat 네임스페이스의 알림 설정 예시입니다.
{
"gatewayNamespaceId": "grn:gs2:ap-northeast-1:YourOwnerId:gateway:default",
"enableTransferMobileNotification": true,
"mobileNotificationMessages": [
{"locale": "ja", "title": "新着メッセージ", "message": "チャットに新しいメッセージが届きました"},
{"locale": "en", "title": "New message", "message": "You have a new chat message"},
{"locale": "default", "title": "New message", "message": "You have a new chat message"}
]
}GS2-Gateway의 sendNotification / sendNotificationByOwnerId / batchSendNotification (엔트리별) / sendMobileNotificationByUserId API에서도 동일한 mobileNotificationMessages 파라미터를 payload와는 별도로, 생략 가능한 인자로 지정할 수 있습니다.
모바일 푸시로 전달할 때의 우선순위는 다음과 같습니다.
mobileNotificationMessages(비어 있지 않은 경우)payload의mobile배열- 원본
payload
게임 내 (WebSocket)로의 전달에는 영향을 주지 않습니다. WebSocket 알림에는 payload가 그대로 포함됩니다.
WebSocket API 설정
GS2의 클라이언트 SDK (Unity / Unreal Engine / Godot)에는 GS2-Gateway의 WebSocket 접속을 자동으로 확립·유지하는 유틸리티가 내장되어 있습니다.
로그인 시 GatewaySetting을 지정하면, 로그인 처리 흐름에 맞추어 WebSocket 접속과 SetUserId 호출을 자동으로 수행할 수 있습니다.
설정 항목은 다음과 같습니다.
gatewayNamespaceName: 사용할 GS2-Gateway의 네임스페이스 이름allowConcurrentAccess: 동시 접속을 허용할지 여부
트랜잭션 액션
GS2-Gateway에서는 트랜잭션 액션을 제공하지 않습니다.
마스터 데이터 관리
GS2-Gateway는 마스터 데이터를 가지지 않습니다. 네임스페이스 설정에서 Firebase 연계 정보나 로그 설정을 구성합니다.
구현 예제
로그인 시 WebSocket 접속을 확립
GatewaySetting을 지정하여 로그인하면, SDK가 자동으로 WebSocket 세션을 확립하고 SetUserId를 호출하여 사용자ID를 연결합니다.
var gameSession = await gs2.LoginAsync(
new Gs2AccountAuthenticator(
accountSetting: new AccountSetting {
accountNamespaceName = this.accountNamespaceName,
},
gatewaySetting: new GatewaySetting {
gatewayNamespaceName = "namespace-0001",
allowConcurrentAccess = false,
}
),
account.UserId,
account.Password
); const auto Future = Profile->Login(
MakeShareable<Gs2::UE5::Util::IAuthenticator>(
new Gs2::UE5::Util::FGs2AccountAuthenticator(
AccountNamespaceName,
KeyId,
"namespace-0001", // gatewayNamespaceName
false // allowConcurrentAccess
)
),
UserId,
Password
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;
const auto Result = Future->GetTask().Result();var authenticator = Gs2AccountAuthenticator.new(
"account-namespace-0001",
"grn:gs2:{region}:{ownerId}:key:namespace-0001:key:key-0001",
"gateway-namespace-0001",
false
)
var game_session = Gs2GameSession.new(
authenticator, connection, account.user_id, account.password
)
var async_result = await game_session.login()
if async_result.error != null:
push_error(str(async_result.error))
return명시적으로 사용자ID를 설정
이미 접속되어 있는 WebSocket 세션에 사용자ID를 연결하고 싶은 경우나, 로그인 후에 동시 접속 허용 상태를 변경하고 싶은 경우에는 SetUserId를 호출합니다.
var domain = await gs2.Gateway.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).WebSocketSession(
).SetUserIdAsync(
allowConcurrentAccess: false
); const auto Future = Gs2->Gateway->Namespace(
"namespace-0001" // namespaceName
)->Me(
GameSession
)->WebSocketSession(
)->SetUserId(
false // allowConcurrentAccess
);
Future->StartSynchronousTask();
if (Future->GetTask().IsError()) return false;var domain = ez.gateway.namespace_(
"$hash"
).me(game_session).web_socket_session(
)
var async_result = await domain.set_user_id(
true, # allow_concurrent_access
null # session_id
)
if async_result.error != null:
push_error(str(async_result.error))
return
var result = async_result.result보다 실전적인 정보
동시 접속 끊김 처리
allowConcurrentAccess: false 설정으로 접속 중일 때 다른 단말기에서 동일 사용자가 로그인을 수행하면, 현재의 WebSocket 세션이 끊어집니다.
클라이언트는 접속 끊김 이벤트를 감지하여, 재로그인을 유도하는 화면으로 전환하거나 “다른 단말기에서 로그인되었습니다"라는 메시지를 표시하는 등의 대응을 해야 합니다.
이를 통해 여러 단말기에서의 동시 플레이를 실질적으로 금지하는 운영이 가능해집니다.
버전 업데이트 시 전체 플레이어 접속 끊기
GS2-Version과 조합하여, 신규 버전 공개 시 모든 플레이어의 WebSocket 세션을 끊음으로써, 재접속 시점에 강제로 버전 체크를 통과시키는 운영이 가능합니다.
자세한 내용은 GS2-Version의 “버전 업데이트 운영 절차"를 참조하십시오.