메신저 웹뷰 H5 게임용 원격 치트 콘솔
메신저 플랫폼의 인스턴트 게임으로 출시한 H5 게임은 대부분 모바일 메신저 앱의 웹뷰 안에서 실행된다. 이 환경에서는 브라우저 개발자 도구를 열 수 없어 디버깅이 어렵고, QA 중에 게임 상태를 바꿔 볼 방법도 없다. 그래서 웹 브라우저에서 원격으로 게임을 조작하는 치트 콘솔을 만들었다.
원격 환경과 실시간으로 주고받아야 하므로 통신은 채팅 구현에 많이 쓰는 이벤트 기반 WebSocket 라이브러리인 socket.io(당시 2.x)를 썼다.
구성 요소
섹션 제목: “구성 요소”| 구성 요소 | 역할 |
|---|---|
| 게임 클라이언트 | 빌드 때 넣어 둔 치트 서버 주소로 접속한다. 자기 상태 데이터를 서버로 보내고, 받은 액션을 실행한다. |
| 프록시 서버 | Node.js(express) 위에 socket.io를 올린 서버다. 접속한 클라이언트 목록을 들고, 콘솔과 클라이언트 사이의 메시지를 양방향으로 중계한다. |
| 운영자 콘솔 | React로 만든 웹 UI다. 클라이언트를 고르고, 클라이언트가 보낸 데이터를 보여 주고, 미리 정의한 액션 버튼을 제공한다. |
동작 흐름
섹션 제목: “동작 흐름”- 게임 클라이언트가 실행되면 치트 서버에 접속한다. 서버는 클라이언트를 구분할 식별자를 만들어 토큰으로 내려 준다.
- 서버는 접속한 클라이언트 목록을 유지한다.
- 운영자가 콘솔에 접속하면 서버는 아직 콘솔과 짝을 이루지 않은 클라이언트 목록을 보여 준다.
- 콘솔에서 클라이언트를 고르면 콘솔과 클라이언트가 짝으로 묶인다. 이후 클라이언트가 보내는 데이터가 콘솔에 표시된다.
- 콘솔에서 액션 버튼을 누르면 액션과 관련 데이터가 서버를 거쳐 클라이언트로 간다.
- 클라이언트는 자기 코드에 구현된 해당 액션을 실행한다. 실제 치트 효과는 클라이언트가 게임 서버에 보내는 요청으로 일어난다.
설계 결정
섹션 제목: “설계 결정”- 치트 로직은 클라이언트에 둔다. 콘솔은 실행할 수 있는 액션 목록과 클라이언트가 보낸 데이터를 보여 주기만 한다.
- 치트 효과는 게임 서버의 정상 요청 경로를 탄다. 콘솔이 게임 서버에 직접 접근하지 않는다.
- 콘솔 UI는 개발용이라 최소한으로 만들었다. 목적을 달성하는 데 필요한 화면만 둔다.
- 로컬 개발에서는 개발 서버 스크립트가 치트 서버를 함께 띄운다. dev·QA 환경의 치트 서버는 CI 작업으로 따로 띄운다.
socket.io로 짝 연결 구현하기
섹션 제목: “socket.io로 짝 연결 구현하기”콘솔과 클라이언트의 일대일 중계는 socket.io의 room으로 표현할 수 있다.
- 서버의 각 소켓은 자기 id와 같은 이름의 room에 자동으로 들어간다. 그래서 특정 소켓 하나에만 보낼 때
io.to(socketId).emit(...)을 쓸 수 있다(Server API: socket.id).socket.id는 재접속할 때마다 바뀌는 임시 값이므로, 콘솔에 보여 줄 클라이언트 식별자는 서버가 따로 발급한 토큰을 쓴다. - room은 서버에만 있는 개념이다. 클라이언트는 자기가 어느 room에 들어가 있는지 모른다(Rooms). 짝 정보는 서버가 관리한다.
- 콘솔용과 게임 클라이언트용 연결을 나누고 싶으면 namespace(
io.of('/console')같은 경로 단위의 소켓 묶음)를 쓸 수 있다(Server API: Namespace).
// 서버: 콘솔이 고른 클라이언트에게 액션을 넘기고, 실행 결과를 콘솔로 되돌린다consoleSocket.on('action', ({ token, name, payload }, reply) => { const clientSocketId = clientsByToken.get(token); io.to(clientSocketId).timeout(5000).emit('action', { name, payload }, (err, results) => { reply(err ? { ok: false } : results[0]); });});emit의 마지막 인자로 함수를 넘기면 상대가 처리한 뒤 그 함수를 호출해 응답을 돌려준다. socket.io는 이것을 acknowledgement라고 부른다. 응답 대기 시간 제한(timeout())은 v4.4.0부터 쓸 수 있다(Emitting events: Acknowledgements). room으로 브로드캐스트하면서 ack를 받으면 콜백은 클라이언트별 응답 배열을 받는다(Emit cheatsheet).