NDC Python 게임서버 안녕하십니까? : 몬스터 슈퍼리그 게임 서버 편의 후속으로 기획된 발표입니다. 사내 준비 도중 "너굴" 님의 질문에서 시작되었습니다.
이 발표는 잘 알려진 RPC Framework 인 Thrift, gRPC를 살펴보고 예시로 오델로 게임을 만들어보면서 기존 RPC framework 들이 게임의 서버/클라 구조에 잘 어울리지는 살펴보고 왜 몬스터 슈퍼리그에서 그런 선택을 했는지 살펴봅니다.
그리고 게임에 맞게 RPC 를 설계하고 이를 이용하여 온라인 오델로 게임을 완성해봅니다.
[야생의 땅: 듀랑고] 서버 아키텍처 - SPOF 없는 분산 MMORPG 서버Heungsub Lee
NDC14에서 발표한 "[야생의 땅: 듀랑고] 서버 아키텍처" 세션의 슬라이드입니다.
슬라이드에 설명이 많지 않은데, 디스이즈게임에서 발표 내용을 잘 정리해주었습니다. 기사도 함께 보시면 좋을 것 같습니다.
http://www.thisisgame.com/webzine/news/nboard/4/?n=54955
NHN NEXT 게임 서버 프로그래밍 강의 자료입니다. 최소한의 필요한 이론 내용은 질문 위주로 구성되어 있고 (답은 학생들 개별로 고민해와서 피드백 받는 방식) 해당 내용에 맞는 실습(구현) 과제가 포함되어 있습니다.
참고로, 서버 아키텍처에 관한 과목은 따로 있어서 본 강의에는 포함되어 있지 않습니다.
오픈 소스 Actor Framework 인 Akka.NET 을 통해 온라인 게임 서버를 어떻게 구현할 수 있는지를 설명합니다. Actor Model 에 대한 기본 이해부터 Scale-out 가능한 게임 서버 구축까지 전반적인 내용에 대해 알 수 있습니다. 설명을 위해 클라이언트는 Unity3D 를 사용할 예정입니다.
멀티플레이어 게임을 서비스하는 데 필요한 게임 장르별 백엔드 아키텍처에 대한 설명해 드립니다. 기본적인 게임의 상태 동기화 개념과 서버 구성에 관한 이야기, 게임 클라이언트 엔진(Unity, Lumberyard, Unreal Engine 등)에서 제공하는 복제 프레임워크를 통하여 손쉽게 게임 서버를 만드는 방법에 대한 내용을 다룹니다. 또한, 이렇게 만들어진 게임 서버를 Amazon GameLift라는 클라우드 서비스를 통해 DevOps형태의 비용 효율적으로 서비스하는 방법에 대해 소개합니다.
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...Amazon Web Services Korea
서비스 런칭을 위해 라이온하트와 카카오게임즈가 어떻게 최적 성능의 인스턴스를 선택하고, Windows 운영 체제를 최적화하며, 왜 Amazon Aurora를 기본 데이터베이스로 채택하였는지를 설명합니다. 또한, 출시부터 운영까지의 과정에서 MMORPG가 어떻게 AWS 상에서 설계되고, 게임 서버 성능을 극대할 수 있었는지에 대해 전달해드립니다.
머신러닝 및 데이터 과학 연구자를 위한 python 기반 컨테이너 분산처리 플랫폼 설계 및 개발Jeongkyu Shin
머신러닝 및 데이터 과학 분야의 컴퓨팅 수요는 해가 갈수록 급증하고 있습니다. 이와 더불어 분산처리 기술, 데이터 파이프라이닝 및 개발 환경 스택 관리 등의 관련된 다양한 이슈들 또한 엄청나게 늘어나고 있습니다. 머신러닝 모델의 기하급수적인 모델 복잡도 증가 추세와 마찬가지로, 모델 학습을 위한 환경 관리 또한 갈수록 복잡도가 높아지는 추세입니다.
이 세션에서는 이러한 문제를 해결하기 위해 python 언어 기반의 분산처리 스케쥴링/오케스트레이션 미들웨어 플랫폼을 개발한 4년간의 과정에서 겪은 다양한 문제들에 대해 다룹니다. 2015년 컨테이너 기반의 고밀도 분산처리 플랫폼 설계 및 프로토타이핑 과정을 PyCon KR에서 발표한 이후, 실제 구현 및 오픈소스화, 안정화를 거치며 겪은 다양한 기술적/비기술적 문제들에 대한 경험을 공유합니다.
기술적으로는 최근 몇 년 간의 클러스터 플랫폼 관련 기술의 진보와 함께 탄생한 다양한 도구들과, 이러한 도구들을 python 기반으로 엮어내기 위해 사용하고 개발한 다양한 오픈소스들을 다룹니다. Python 기반의 컨테이너 스케쥴링 및 오케스트레이션 과정의 구현과, 다양한 프로그래밍 언어로 만든 SDK를 graphQL을 이용하여 연동하는 과정에서의 몇몇 유의점을 설명합니다. 아울러 python 기반의 SDK를 다양한 언어로 포팅했던 경험을 간단하게 안내합니다.
플랫폼을 개발하는 중 등장한 TensorFlow, PyTorch 등의 다양한 머신러닝 프레임워크들을 도입하며 겪은 문제와 해결 과정에 대해서도 나눕니다. 연구 분야에는 Python 2.7 기반의 프레임워크들이 여전히 많습니다. 이러한 프레임워크 및 라이브러리의 지원을 위하여 Python 2 기반의 프레임워크와 Python 3.7로 구현한 컨테이너 인터페이스를 단일 컨테이너 환경에 중복 빌드 및 상호 간섭 없이 공존시키기 위해 개발한 아이디어를 소개합니다.
마지막으로 Python 기반의 프레임워크를 개발, 배포 및 상용화 하는 과정에서 겪은 다양한 어려움을 소개합니다. 솔루션을 배포 및 보급할 때 겪는 다양한 런타임, 하드웨어 환경 및 개인 정보 보호를 위한 폐쇄망 대상의 디플로이 등에 대응하기 위하여 Python 응용프로그램을 단독 실행용으로 패키징하는 과정에서 겪은 팁들을 설명합니다. 또한 GUI 빌드 및 Python, Go 및 C++을 함께 사용한 드라이버 가상화 레이어 개발 등의 내용도 살짝 다룹니다.
이 슬라이드는 PyCon KR 2019의 발표 슬라이드입니다. ( https://www.pycon.kr/program/talk-detail?id=138 )
[야생의 땅: 듀랑고] 서버 아키텍처 - SPOF 없는 분산 MMORPG 서버Heungsub Lee
NDC14에서 발표한 "[야생의 땅: 듀랑고] 서버 아키텍처" 세션의 슬라이드입니다.
슬라이드에 설명이 많지 않은데, 디스이즈게임에서 발표 내용을 잘 정리해주었습니다. 기사도 함께 보시면 좋을 것 같습니다.
http://www.thisisgame.com/webzine/news/nboard/4/?n=54955
NHN NEXT 게임 서버 프로그래밍 강의 자료입니다. 최소한의 필요한 이론 내용은 질문 위주로 구성되어 있고 (답은 학생들 개별로 고민해와서 피드백 받는 방식) 해당 내용에 맞는 실습(구현) 과제가 포함되어 있습니다.
참고로, 서버 아키텍처에 관한 과목은 따로 있어서 본 강의에는 포함되어 있지 않습니다.
오픈 소스 Actor Framework 인 Akka.NET 을 통해 온라인 게임 서버를 어떻게 구현할 수 있는지를 설명합니다. Actor Model 에 대한 기본 이해부터 Scale-out 가능한 게임 서버 구축까지 전반적인 내용에 대해 알 수 있습니다. 설명을 위해 클라이언트는 Unity3D 를 사용할 예정입니다.
멀티플레이어 게임을 서비스하는 데 필요한 게임 장르별 백엔드 아키텍처에 대한 설명해 드립니다. 기본적인 게임의 상태 동기화 개념과 서버 구성에 관한 이야기, 게임 클라이언트 엔진(Unity, Lumberyard, Unreal Engine 등)에서 제공하는 복제 프레임워크를 통하여 손쉽게 게임 서버를 만드는 방법에 대한 내용을 다룹니다. 또한, 이렇게 만들어진 게임 서버를 Amazon GameLift라는 클라우드 서비스를 통해 DevOps형태의 비용 효율적으로 서비스하는 방법에 대해 소개합니다.
오딘: 발할라 라이징 MMORPG의 성능 최적화 사례 공유 [카카오게임즈 - 레벨 300] - 발표자: 김문권, 팀장, 라이온하트 스튜디오...Amazon Web Services Korea
서비스 런칭을 위해 라이온하트와 카카오게임즈가 어떻게 최적 성능의 인스턴스를 선택하고, Windows 운영 체제를 최적화하며, 왜 Amazon Aurora를 기본 데이터베이스로 채택하였는지를 설명합니다. 또한, 출시부터 운영까지의 과정에서 MMORPG가 어떻게 AWS 상에서 설계되고, 게임 서버 성능을 극대할 수 있었는지에 대해 전달해드립니다.
머신러닝 및 데이터 과학 연구자를 위한 python 기반 컨테이너 분산처리 플랫폼 설계 및 개발Jeongkyu Shin
머신러닝 및 데이터 과학 분야의 컴퓨팅 수요는 해가 갈수록 급증하고 있습니다. 이와 더불어 분산처리 기술, 데이터 파이프라이닝 및 개발 환경 스택 관리 등의 관련된 다양한 이슈들 또한 엄청나게 늘어나고 있습니다. 머신러닝 모델의 기하급수적인 모델 복잡도 증가 추세와 마찬가지로, 모델 학습을 위한 환경 관리 또한 갈수록 복잡도가 높아지는 추세입니다.
이 세션에서는 이러한 문제를 해결하기 위해 python 언어 기반의 분산처리 스케쥴링/오케스트레이션 미들웨어 플랫폼을 개발한 4년간의 과정에서 겪은 다양한 문제들에 대해 다룹니다. 2015년 컨테이너 기반의 고밀도 분산처리 플랫폼 설계 및 프로토타이핑 과정을 PyCon KR에서 발표한 이후, 실제 구현 및 오픈소스화, 안정화를 거치며 겪은 다양한 기술적/비기술적 문제들에 대한 경험을 공유합니다.
기술적으로는 최근 몇 년 간의 클러스터 플랫폼 관련 기술의 진보와 함께 탄생한 다양한 도구들과, 이러한 도구들을 python 기반으로 엮어내기 위해 사용하고 개발한 다양한 오픈소스들을 다룹니다. Python 기반의 컨테이너 스케쥴링 및 오케스트레이션 과정의 구현과, 다양한 프로그래밍 언어로 만든 SDK를 graphQL을 이용하여 연동하는 과정에서의 몇몇 유의점을 설명합니다. 아울러 python 기반의 SDK를 다양한 언어로 포팅했던 경험을 간단하게 안내합니다.
플랫폼을 개발하는 중 등장한 TensorFlow, PyTorch 등의 다양한 머신러닝 프레임워크들을 도입하며 겪은 문제와 해결 과정에 대해서도 나눕니다. 연구 분야에는 Python 2.7 기반의 프레임워크들이 여전히 많습니다. 이러한 프레임워크 및 라이브러리의 지원을 위하여 Python 2 기반의 프레임워크와 Python 3.7로 구현한 컨테이너 인터페이스를 단일 컨테이너 환경에 중복 빌드 및 상호 간섭 없이 공존시키기 위해 개발한 아이디어를 소개합니다.
마지막으로 Python 기반의 프레임워크를 개발, 배포 및 상용화 하는 과정에서 겪은 다양한 어려움을 소개합니다. 솔루션을 배포 및 보급할 때 겪는 다양한 런타임, 하드웨어 환경 및 개인 정보 보호를 위한 폐쇄망 대상의 디플로이 등에 대응하기 위하여 Python 응용프로그램을 단독 실행용으로 패키징하는 과정에서 겪은 팁들을 설명합니다. 또한 GUI 빌드 및 Python, Go 및 C++을 함께 사용한 드라이버 가상화 레이어 개발 등의 내용도 살짝 다룹니다.
이 슬라이드는 PyCon KR 2019의 발표 슬라이드입니다. ( https://www.pycon.kr/program/talk-detail?id=138 )
이 발표는 [야생의 땅: 듀랑고]의 지형 배포 시스템과 생태계 시뮬레이션 자동화 시스템에 대한 이야기를 다룹니다. 듀랑고의 각 섬은 크기와 지형, 기후 조건이 다양하고 섬의 개수가 많아서 수동으로 관리하는 것은 사실상 불가능합니다. 몇번의 사내 테스트와 베타 테스트를 거치면서 이러한 문제를 해결해주는 자동화된 도구의 필요성이 절실해졌고, 작년에 NDC에서 발표했던 생태계 시뮬레이터와 Docker, 그리고 아마존 웹서비스(AWS)를 이용하여 수많은 섬들을 자동으로 생성하고 관리하는 자동화 시스템을 구축하게 되었습니다. 그 과정에서 했던 고민들, 기존의 애플리케이션을 "Dockerizing" 했던 경험, AWS의 각 서비스들을 적절히 활용했던 이야기, AWS의 각 지역별 요금이 상이하다는 점을 이용해서 비용을 절감한 사례, 그리고 자동화 시스템의 문제점과 앞으로의 방향에 대해서 이야기 할 계획입니다.
GDB와 strace로 Hang 걸린 Python Process 원격 디버깅Youngmin Koo
Python 프로그램을 디버깅하실 때 어떤 툴을 사용하시나요? 아무래도 가장 많이 사용하고 계신 툴은 PyCharm이 아닐까 싶습니다. PyCharm은 JetBrains에서 만든 GUI 환경에서 사용할 수 있는 Python IDE입니다.
PyCharm은:
로컬 컴퓨터에서 디버깅 모드로 (PyDev로) Python 프로세스를 실행할 수 있습니다.
로컬 컴퓨터에서 실행 중인 Python 프로세스에 Attach해 디버깅할 수 있습니다.
원격 서버에서 디버깅 모드로 (PyDev로) Python 프로세스를 실행할 수 있습니다.
하지만 원격에서 동작하고 있는 프로세스에 디버거를 Attach하는 기능은 제공하지 않고 있습니다. 또한 Python 디버깅 모듈인 pdb를 사용하여도 동작하고 있는 프로세스에 Attach하는 것은 지원하고 있지 않습니다.
로컬 환경에서는 문제 없이 돌아갔던 Python 프로그램이 원격 서버에서는 아무런 로그 없이 멈춰 버리는 경우 어디서부터 손을 대야 할지 정말 막막합니다. 이 때 GDB와 strace를 이용하면 어디에서 문제가 발생했는지 진단할 수 있습니다. 이 세션에서는 GDB와 strace를 이용해 디버깅하여 원격 리눅스 서버에서 Python Process가 Hang 되어 버리는 문제를 진단하고 해결했던 경험을 공유하려고 합니다.
빌드? 우선 사용부터 매뉴얼? Getting started 한 번 돌려보기 TV 리모컨 버튼 5개 전문가는 교육받아 만들어진다? 경험=시간+시행착오+성공실패 오픈소스 트러블슈팅 “메시지” 구글링 오픈소스 함부로 수정하지 마라 최신 버전을 대하는 우리의 자세 LTS로 대동단결 팀장 설득하기 오픈소스는 공짜가 아닙니다. 저도 기여하고 싶어요 2,000년 톰캣을 시작으로 Ant, Eclipse, JUnit, JMeter를 거쳐 현재 개발에 잘 사용하고 있는 Yona, Git, VSCode, Jenkins, CentOS, VirtualBox, Nginx, Node.js, Express.js, MariaDB, Uptime, Mocha, SonarQube, ZAP 이야기 등입니다.
https://www.youtube.com/watch?v=5LHOTBxG0hc
[KGC2014] 두 마리 토끼를 잡기 위한 C++ - C# 혼합 멀티플랫폼 게임 아키텍처 설계Sungkyun Kim
이미 많은 개발자들이 C#의 장점을 누리고 있으나, 본 PT에서는 높은 성능과 생산성을 동시에 달성하기 위해 C/C++로 개발된 native 게임 코드에 스크립트 언어로서 C#을 통합 할 수 있는 방법을 제시한다. 이를 위해 오픈소스 .Net 프레임웍인 Mono의 사용방법과 모바일 플랫폼에서의 특이사항들을 자세히 설명한다.
또한, C/C++언어에 C#을 비롯한 다양한 스크립트 언어를 효율적으로 혼합하여 게임을 구현할 수 있는 아키텍처를 제시한다. clang과 reflection을 이용하여 서로 다른 언어 간 인터페이스 노출을 자동화하고, 게임 내 오브젝트의 생명주기를 자동으로 관리할 수 있는 기법에 대해 설명한다.
[IGC 2017] 넥스트플로어 김영수 - Protocol:hyperspace Diver 개발 포스트모템강 민우
본 세션에서는 Protocol:hyperspace Diver의 개발 과정 전반에 대한 포스트 모템을 수행하며 기획적인 부분을 바탕으로 제기된 요구사항에 대응하기 위한 기술적인 이슈에 어떻게 대응하였는지를 살펴볼 예정입니다. 게임을 기획하며 게임에 어떤 기능들이 요구되었으며, 엔진 레벨에서부터 모바일 게임을 개발하는 과정에서 이런 요구사항들에 어떻게 대응하였는지를 살펴봅니다. 게임을 위한 전체적인 설계 및 문제 해결 전략과 각각의 문제 해결 과정에서 세부 내용에 대한 기술적 노하우를 공유합니다.
Auto Scalable 한 Deep Learning Production 을 위한 AI Serving Infra 구성 및 AI DevOps...hoondong kim
[Tensorflow-KR Offline 세미나 발표자료]
Auto Scalable 한 Deep Learning Production 을 위한 AI Serving Infra 구성 및 AI DevOps Cycle 구성 방법론. (Azure Docker PaaS 위에서 1만 TPS Tensorflow Inference Serving 방법론 공유)
Similar to Python 게임서버 안녕하십니까 : RPC framework 편 (20)
2. 왜?
NDC 발표 (Python 게임서버 안녕하십니까? : 몬스터 슈퍼리그 게임 서버)
준비중에 사내 리뷰 과정에서 “너굴” 님의 질문으로부터 시작
너굴 : “게임 서버/클라 네트워킹 에서 RPC framework 를
사용하지 않고 직접 구현하신 이유가 있나요?”
준곰 : “어쩌고 저쩌고… 그래서 어쩌고저쩌고”
너굴 : “네…”
4. 목표
• RPC framework 에 대한 정보 공유
• RPC framework 를 게임에 적용해보자
• 몬스터 슈퍼리그 에서 사용한 방식 공유
• 게임에 적합한 방식을 직접 만들어 보는 것
5. 목차
• RPC
• Thrift , gRPC
• 몬스터 슈퍼리그 방식
• 게임에 맞게 RPC 만들기
• 정리 & 생각해볼 만한 것들
마스터, 준비 되었나요?
이제 시작합니다.
졸면 안되요!
6. RPC
• Remote Procedure Call
• wikipedia : In distributed computing, a remote procedure call (RPC)
is when a computer program causes a procedure (subroutine) to
execute in another address space (commonly on another computer
on a shared network), which is coded as if it were a normal (local)
procedure call, without the programmer explicitly coding the details
for the remote interaction.
7. RPC
• Remote Procedure Call
• wikipedia : In distributed computing, a remote procedure call (RPC)
is when a computer program causes a procedure (subroutine) to
execute in another address space (commonly on another computer
on a shared network), which is coded as if it were a normal (local)
procedure call, without the programmer explicitly coding the details
for the remote interaction.
네트워크 상태나 콜 방식을 신경쓰지 않고
프로그래머가 원격의 함수를 실행하는 것
9. RPC
1. Procedure name
2. Parameters
3. Networking
4. Protocol (message)
•IDL (Interface Definition Language) 로
정의
•IDL 은 RPC framework 별로 다르지만 ,
built-in type 은 대부분 비슷하게 지원
•단, 지원하는 container 의 차이, signed,
unsigned 지원의 차이는 있음
10. RPC
1. Procedure name
2. Parameters
3. Networking
4. Protocol (message)
•Networking 방식의 차이 따라
Procedure Call 과 return 처리 방식이 달
라짐
•message 의 (de)serializer 차이에 따라
Protocol 의 성능이나 보안의 차이가 존재
11. 게임에서 RPC 선택
•IDL 정의를 서버/클라이언트가 코드 레벨에서 공유할 수 있나? ( 컴파일
타임에 오류 확인이 가능한 방식을 선호, 코드를 생성해주는 RPC
framework 의 IDL )
•클라이언트에서 async call 을 지원해야 하며 return 의 형태나 return 의
처리 과정에 개입할 수 있나?
•Unity (.Net 2.0, .Net 3.5, C# 4) , C++ 지원하나?
•json, xml을 사용하지 않고 빠른 자체 message protocol 지원하는가?
13. Thrift (θrift)
•“scalable cross-language services development” 를 위해 Facebook
에서 개발, RPC framework 로 사용됨
•다양한 언어를 지원 ( https://thrift.apache.org/lib/ )
•built-in type 외에 다양한 container 지원
( https://thrift.apache.org/docs/types )
•하지만, 부족한 문서는 가장 큰 단점 ( Thrift: The Missing Guide
https://diwakergupta.github.io/thrift-missing-guide/ )
14. Thrift
•Server, Processor, Protocol, Transport 로 구성
•Thrift 를 통해서 code 생성을 하면 RPC Client 코드도 생성
•서버 / 클라이언트 의 가장 큰 차이는 당연하게도 Processor 유무
•Protocol, Transport 는 각각 Serialization 과 Networking 을 담당
17. 준곰
• 스마트스터디의 CTO 로 몬스터 슈퍼리그 개발에 참여했습니다.
• 넥슨에서 게임을 즐겁게 만드는 방법을 배웠습니다.
• 엔씨소프트에서 게임을 잘 만드는 방법을 배웠습니다.
• 네오위즈게임즈에서 게임을 처음부터 만들고 끝까지 완성하는
방법을 배웠습니다.
• 스마트스터디에서는 게임을 만들어 성공하는 방법을 배웠습니
다.
19. Othello (오델로)
•Reversi(리버시) 라고도 부르는 보드게임
•두 명이 8x8 오델로 판 위에서 흑, 백 돌을 번갈아 놓으면서 진행
•처음에 판 가운데에 사각형으로 엇갈리게 배치된 돌 4개를 놓고 시작한다.
•돌은 반드시 상대방 돌을 양쪽에서 포위하여 뒤집을 수 있는 곳에 놓아야 한다.
•돌을 뒤집을 곳이 없는 경우에는 차례가 자동적으로 상대방에게 넘어가게 된다.
•아래와 같은 조건에 의해 양쪽 모두 더 이상 돌을 놓을 수 없게 되면 게임이 끝
나게 된다.
• 64개의 돌 모두가 판에 가득 찬 경우 (가장 일반적)
• 어느 한 쪽이 돌을 모두 뒤집은 경우
• 한 차례에 양 쪽 모두 서로 차례를 넘겨야 하는 경우
•게임이 끝났을 때 돌이 많이 있는 플레이어가 승자가 된다. 만일 돌의 개수가 같
을 경우는 무승부가 된다.
wikipedia
26. othello.server.py
import asyncio
import thriftpy
from aiothrift.server import create_server
# ...
othello = thriftpy.load('othello.thrift', module_name='othello_thrift')
# ...
class OthelloServer:
# ...
def run_forever(self):
self.loop = asyncio.get_event_loop()
self.server = self.loop.run_until_complete(
create_server(othello.OthelloService, Dispatcher(self),
address=(self.ip, self.port), loop=self.loop, protocol_cls=TBinaryProtocol)
self.loop.run_forever()
•aiothrift 로 Server 구성 (@asyncio.coroutine)
• Server, Processor, Protocol, Transport 재작성
• asyncio event_loop, open_connection 사용
27. othello.server.py
class Dispatcher:
# ...
@db_transaction
def Login(self, platform_type, platform_token):
# ...
return user
@db_transaction
def Register(self, platform_type, platform_token, name):
# ...
return user
def db_transaction(func):
@wraps(func)
def _impl(self, *args, **kwargs):
ret = None
try:
self.db.begin_session()
ret = func(self, *args, **kwargs)
self.db.commit()
except Exception as e:
self.db.rollback()
raise e
finally:
self.db.end_session()
return ret
return _impl
•processor handler 는 service 정의
대로 작성
•db transaction 을 processor 에 반
영하기 위해 decorator
(db_transaction) 를 작성
•RPC 는 오류가 있는 경우 raise
Exception 을 하므로 이를 기준으로
commit, rollback
28. 문제
•Server
•1 user 는 1개의 session 만 유지해야 하는 방법 필요
•중복 요청 방지를 위한 방법이 필요
•Client
•C# 으로 생성된 Client 코드는 TSocket 을 사용 (blocked-io) 이는
synchronous
29. 시도
•인증 전 후로 사용 가능한 Procedure 를 분리하고 인증 전에는 session
생성을 인증 후에는 session 체크를 하는 로직을 작성
•Processor 에 session token 생성, 체크 작성
•Protocol 에 session token 기본 포함, 생성된 코드에서 매번 session
token 을 넣지 않도록 작성
30. 시도
•인증 전 후로 사용 가능한 Procedure 를 분리하고 인증 전에는 session
생성을 인증 후에는 session 체크를 하는 로직을 작성
•Processor 에 session token 생성, 체크 작성
•Protocol 에 session token 기본 포함, 생성된 코드에서 매번 session
token 을 넣지 않도록 작성
빠른 포기!!!
•IDL을 기준으로 코드 생성이 되므로 IDL 에 없는 상태에서 이를 반영하기
위해서는 Protocol 을 수정할 필요가 있음
•Protocol 에서의 인증 절차 등이 필요함
31. 시도
•C# 의 생성된 service 코드를 coroutine 으로
•Thrift 가 생성한 코드는 async 처리가 불가능, async 한 처리를 위해서
Unity coroutine 코드가 필요
•TSocket 등 Transport 도 coroutine 으로 작성되어 있지 않음
•생성된 코드기준으로 async, await 등은 C# 5 이상 필요한 만큼 async
call 을 사용할 수 없음
32. 시도
•C# 의 생성된 service 코드를 coroutine 으로
•Thrift 가 생성한 코드는 async 처리가 불가능, async 한 처리를 위해서
Unity coroutine 코드가 필요
•TSocket 등 Transport 도 coroutine 으로 작성되어 있지 않음
•생성된 코드기준으로 async, await 등은 C# 5 이상 필요한 만큼 async
call 을 사용할 수 없음
빠른 포기!!!
•TSocket, Transport 를 coroutine 으로 새로 작성해야 함
34. gRPC
• google 에서 개발
• Transport 로 HTTP/2 지원
• 양방향 streaming 지원
• IDL 로 google Protocol Buffers 사용
• 서버 클라이언트 모두 sync, async 방식 제공
• Protocol 레벨에서 인증 기능 제공 ( https://grpc.io/docs/guides/auth.html )
• 다양한 언어 지원 ( https://grpc.io/docs/quickstart/ )
35. protobuf(Protocol Buffers)
•상세한 문서 ! ( https://developers.google.com/protocol-buffers/ )
•signed, unsigned 지원
•uint32, uint64
•nested type 지원
•message { message { enum { } } }
•repeated (list), map container 지원
36. othello.proto
•unsigned 를 지원하므로 적절하게 이용
•Exception 이 없으므로 ResultCode 를 만들어 사용
•procedure 에 사용하는 parameter 와 return value 는 별도로 정의
syntax = "proto3";
package othello;
message User {
uint64 id = 1;
string token = 2;
string name = 3;
uint32 level = 4;
uint32 exp = 5;
uint32 win = 6;
uint32 lose = 7;
uint32 gold = 8;
}
enum PlatformType {
UNKNOWN = 0;
CUSTOM = 1;
GAME_CENTER = 2;
GOOGLE_PLAY = 3;
FACEBOOK = 4;
}
enum ResultCode {
Success = 0;
ErrorUserNotRegistered = 100;
ErrorUserNameAlreadyExists = 101;
ErrorUserAlreadyExists = 102;
ErrorUserInvalidName = 103;
ErrorSystem = 200;
}
37. othello.proto
•unsigned 를 지원하므로 적절하게 이용
•Exception 이 없으므로 ResultCode 를 만들어 사용
•procedure 에 사용하는 parameter 와 return value 는 별도로 정의
syntax = "proto3";
package othello;
message User {
uint64 id = 1;
string token = 2;
string name = 3;
uint32 level = 4;
uint32 exp = 5;
uint32 win = 6;
uint32 lose = 7;
uint32 gold = 8;
}
enum PlatformType {
UNKNOWN = 0;
CUSTOM = 1;
GAME_CENTER = 2;
GOOGLE_PLAY = 3;
FACEBOOK = 4;
}
enum ResultCode {
Success = 0;
ErrorUserNotRegistered = 100;
ErrorUserNameAlreadyExists = 101;
ErrorUserAlreadyExists = 102;
ErrorUserInvalidName = 103;
ErrorSystem = 200;
}
38. othello.proto
•procedure 에 사용하는 parameter 와 return value 는 별도로 정의
•return 값 에는 모두 Result 를 포함하도록 작성
message Result {
ResultCode code = 1;
string message = 2;
}
message ReqLogin {
PlatformType platform_type = 1;
string platform_token = 2;
}
message RspLogin {
Result result = 1;
User user = 2;
}
service Othello {
rpc Login(ReqLogin) returns (RspLogin) {}
rpc Register(ReqRegister) returns (RspRegister) {}
}
39. othello.proto
•procedure 에 사용하는 parameter 와 return value 는 별도로 정의
•return 값 에는 모두 Result 를 포함하도록 작성
message Result {
ResultCode code = 1;
string message = 2;
}
message ReqLogin {
PlatformType platform_type = 1;
string platform_token = 2;
}
message RspLogin {
Result result = 1;
User user = 2;
}
service Othello {
rpc Login(ReqLogin) returns (RspLogin) {}
rpc Register(ReqRegister) returns (RspRegister) {}
}
40. gRPC generate code
•pb2 , grpc 2개의 파일이 생성
•data class 들은 othello_pb2.py
•server, client class 들은 othello_pb2_grpc.py
$ python -m grpc_tools.protoc -I. --python_out=./gen-grpc --grpc_python_out=./gen-grpc ./othello.proto
$ find gen-grpc
gen-grpc
gen-grpc/__init__.py
gen-grpc/othello_pb2.py
gen-grpc/othello_pb2_grpc.py
41. othello.server.py
•Thrift 와 별 차이 없음
•asyncio 잘 지원해주는 package 는 아직 없음
def run_forever(self):
self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
othello_pb2_grpc.add_OthelloServicer_to_server(
Dispatcher(self), self.server)
self.server.add_insecure_port('[::]:{}'.format(self.port))
self.server.start()
try:
while True:
time.sleep(60 * 60 * 24)
except KeyboardInterrupt:
self.server.stop(0)
42. othello.server.py
•Exception 을 지원하지 않고 있으므로 ResultCode 를 만들어 사용
class Dispatcher(othello_pb2_grpc.OthelloServicer):
@db_transaction
def Login(self, request, context):
db_sec = self.session.query(SecurityData).filter(
and_(SecurityData.platform_type==request.platform_type,
SecurityData.platform_token==request.platform_token)).first()
if db_sec is None:
return othello_pb2.RspLogin(
result=othello_pb2.Result(code=othello_pb2.ErrorUserNotRegistered))
# ...
rsp = othello_pb2.RspLogin(
result=othello_pb2.Result(code=othello_pb2.Success),
user=othello_pb2.User())
db_user.fill(rsp.user)
return rsp
43. othello.server.py
•Exception 을 지원하지 않고 있으므로 ResultCode 를 만들어 사용
class Dispatcher(othello_pb2_grpc.OthelloServicer):
@db_transaction
def Login(self, request, context):
db_sec = self.session.query(SecurityData).filter(
and_(SecurityData.platform_type==request.platform_type,
SecurityData.platform_token==request.platform_token)).first()
if db_sec is None:
return othello_pb2.RspLogin(
result=othello_pb2.Result(code=othello_pb2.ErrorUserNotRegistered))
# ...
rsp = othello_pb2.RspLogin(
result=othello_pb2.Result(code=othello_pb2.Success),
user=othello_pb2.User())
db_user.fill(rsp.user)
return rsp
44. 문제
•Python Test Client 작동 확인. 문제는 역시나 Unity C# 지원 문제
•Unity Mono 는 .NET 4.5 가 아니라 .NET 2.0 지원 (Unity2017.1 버전
부터는 .NET 3.5 지원)
•Unity 에서 gRPC 사용을 위한 프로젝트들이 있기는 하지만 생각보다 복
잡하고 무거움 ( https://github.com/neuecc/MagicOnion )
•이럴려고 RPC 를 써보려고 한 건 아닌데
45. 문제
•Python Test Client 작동 확인. 문제는 역시나 Unity C# 지원 문제
•Unity Mono 는 .NET 4.5 가 아니라 .NET 2.0 지원 (Unity2017.1 버전
부터는 .NET 3.5 지원)
•Unity 에서 gRPC 사용을 위한 프로젝트들이 있기는 하지만 생각보다 복
잡하고 무거움 ( https://github.com/neuecc/MagicOnion )
•이럴려고 RPC 를 써보려고 한 건 아닌데
빠른 포기!!!
50. Protocol Buffers
message MsgUserItem
{
optional fixed32 item_uid = 1;
optional uint32 item_count = 2;
}
enum MonsterStatType {
MS_None = 0;
MS_Attack = 1;
MS_Defence = 2;
MS_Heal = 3;
MS_Balance = 4;
MS_Hp = 5;
}
•unsigned, signed type 구분
•Data 로 사용할 것은 Msg, RPC 로 실행될 Procedure 정의는 Req, Rsp
message ReqUserLogin
{
optional AccountPlatformType platform_type = 1;
optional string platform_user_id = 2;
}
// response packet은 RspUserLogin 을 사용한다
message ReqUserRegister
{
// ...
}
51. Protocol Buffers
message Request {
optional uint32 protocolVersion = 1;
optional int32 protocolId = 2;
optional uint32 seqNo = 3;
optional string token = 4;
optional Ticket ticket = 5;
optional ReqUserLogin userLoginReq = 50;
optional ReqUserRegister userRegisterReq = 55;
// ...
}
•Request 를 service 라고 정의, optional 로 모든 Procedure 등록
•인증에 필요한 protocol version, protocol id, seq no, token 을
Request Service 에 공통으로 추가
52. Protocol Buffers
•RPC return 역시 Server —> Client RPC 라고 보고 Response Service
생성, Response Service 에 모든 Procedure 를 등록
•인증에 필요한 정보를 Service 에 공통으로 추가
message Response
{
optional uint32 protocolVersion = 1;
optional int32 protocolId = 2;
optional Result result = 3;
optional Ticket ticket = 4;
optional uint32 reqSeqNo = 5;
optional RspUserLogin userLoginRsp = 50;
optional RspUserRegister userRegisterRsp = 55;
// ...
}
message MultipleResponse
{
repeated Response responses = 1;
optional uint32 reqSeqNo = 2;
optional uint32 nextTicketNo = 3;
}
53. server.route.py
def route(ext):
def decorator(f):
succ = False
for field in request_pb2._REQUEST.fields:
if field.message_type is not None and
field.message_type.name == ext:
route.route_protocol_map[field.number] =
[field.name, f, ext]
succ = True
break
if succ is False:
raise Exception("Unknown Request Packet : %s" % ext)
return f
return decorator
def handle(userContext, req):
if req.protocolId in route.route_protocol_map:
field_name, handler, name =
route.route_protocol_map[req.protocolId]
if req.HasField(field_name):
packet = getattr(req, field_name)
newrelic.agent.set_transaction_name(field_name)
return handler(userContext, packet)
•Procedure 는
@route(“Procedure Name”)
으로 선언
•Request 가 오면 handle 에서
등록되어있는 Procedure 를
Procedure Name 기준으로
실행
55. 몬스터 슈퍼리그
•클라이언트에서 Synchronous Call 을 지원하지 않음
•Procedure Call 만 있을 뿐 return 은 Response 용 Procedure 가 클라
이언트에서 실행 되는 형태, 하지만 양방향통신 이라고 할 수는 없음
•Connection 연결 유지가 필요없는 구조, 서버 클라 모두 부담이 적음
•Protobuf 사용으로 인한 serialize , deserialize 비용이 낮음
•게임 데이터 를 Protobuf 로 생성한 struct 사용
56. 몬스터 슈퍼리그
•클라이언트에서 Synchronous Call 을 지원하지 않음
•Procedure Call 만 있을 뿐 return 은 Response 용 Procedure 가 클라
이언트에서 실행 되는 형태, 하지만 양방향통신 이라고 할 수는 없음
•Connection 연결 유지가 필요없는 구조, 서버 클라 모두 부담이 적음
•Protobuf 사용으로 인한 serialize , deserialize 비용이 낮음
•게임 데이터 를 Protobuf 로 생성한 struct 사용
도전!!!
58. 시도
•게임 개발에 필요한 수준으로 직접 만들어보자
•개발 난이도가 높은 IDL 과 message 의 serializer 는 기존 것을 선택하자
•서버는 http server 를 사용하고 RPC 설계는 몬스터 슈퍼리그 방식을 사
용하자
59. 선택
•IDL : Protocol Buffers (serializer 포함, service 정의는 사용하지 않음)
•Procedure 는 IDL 에 정의한 message 를 활용한다
•ReqLogin, RspLogin
•Request 에 실패한 경우에 대한 공통적인 처리를 작성한다
•RPC response 처리 효율화를 위해 Request / Respone 를 분리한다
•서버는 aiohttp , Protobuf 3.3.0 (Python 3 지원) 을 사용
•클라이언트는 Protobuf 2.6.1 , protobuf-net r668 을 사용
60. othello.proto
•Procedure 를 protobuf IDL 의
message 로 정의
•Client —> Server 의 Procedure 는
Request 라는 message 내에 모두
등록. (Request 는 Service 임)
message ReqLogin {
optional PlatformType platform_type = 1;
optional string platform_token = 2;
}
message RspLogin {
optional User user = 1;
optional string platform_token = 2;
optional string token = 3;
}
message Request {
optional uint32 protocolVersion = 1;
optional int32 protocolId = 2;
optional uint32 seqNo = 3;
optional string token = 4;
optional ReqLogin loginReq = 100;
optional ReqRegister registerReq = 101;
optional ReqCreateGameRoom makeGameRoomReq = 102;
optional ReqExitGameRoom exitGameRoomReq = 103;
optional ReqGamePut gamePutReq = 104;
optional ReqGameSync gameSyncReq = 105;
optional ReqJoinGameRoom joinGameRoomReq = 106;
optional ReqRandomJoin randomJoinReq = 107;
}
61. othello.proto
•Procedure 를 protobuf IDL 의
message 로 정의
•Client —> Server 의 Procedure 는
Request 라는 message 내에 모두
등록. (Request 는 Service 임)
message ReqLogin {
optional PlatformType platform_type = 1;
optional string platform_token = 2;
}
message RspLogin {
optional User user = 1;
optional string platform_token = 2;
optional string token = 3;
}
message Request {
optional uint32 protocolVersion = 1;
optional int32 protocolId = 2;
optional uint32 seqNo = 3;
optional string token = 4;
optional ReqLogin loginReq = 100;
optional ReqRegister registerReq = 101;
optional ReqCreateGameRoom makeGameRoomReq = 102;
optional ReqExitGameRoom exitGameRoomReq = 103;
optional ReqGamePut gamePutReq = 104;
optional ReqGameSync gameSyncReq = 105;
optional ReqJoinGameRoom joinGameRoomReq = 106;
optional ReqRandomJoin randomJoinReq = 107;
}
Procedure
Service
62. othello.proto
•RPC return 값은 Server —> Client
Procedure Call 로 정의하고 Response
message 를 만들어 Procedure 를 모두
등록
•서버 기준에서 한번에 여러 Procedure 를
순서대로 Call 할 수 있도록
multipleResponse message 를 추가
ex) Login 을 다시 했지만 이전 접속 때
진행중인 게임이 있다면 Join
RspLogin , RspJoinGameRoom 두
Response 가 return
message Response
{
optional uint32 protocolVersion = 1;
optional int32 protocolId = 2;
optional Result result = 3;
optional uint32 reqSeqNo = 5;
optional RspLogin loginRsp = 100;
optional RspRegister registerRsp = 101;
optional RspCreateGameRoom makeGameRoomRsp = 102;
optional RspExitGameRoom exitGameRoomRsp = 103;
optional RspGamePut gamePutRsp = 104;
optional RspGameSync gameSyncRsp = 105;
optional RspJoinGameRoom joinGameRoomRsp = 106;
optional RspInternalServerError internalServerErrorRsp = 200;
}
message MultipleResponse
{
optional uint32 reqSeqNo = 1;
repeated Response responses = 10;
}
63. othello.server.py
•aiohttp 는 +_+b 좋음
•health 는 ELB target group 의 health check 용
•api, route, handle 는 몬스터 슈퍼리그의 api 코드 참고
def run_forever(self):
self.server = web.Application()
self.server.router.add_get('/', self.home)
self.server.router.add_get('/health', self.health)
self.server.router.add_post('/api', self.api)
web.run_app(self.server, host=self.ip, port=self.port)
# api , route, handle 은 기존 몬스터 슈퍼리그 에서 사용한 것을 그대로 적용
64. Client.cs
•Procedure Call (SendPacket) 하고 wait 하지 않음
•Server—>Client RPC 실행은 OnPacket 으로 시작하는 handler 들이 순
차적으로 실행
public void RequestLogin(PlatformType platform_type, string platform_token)
{
ReqLogin req = new ReqLogin();
req.platform_type = platform_type;
req.platform_token = platform_token;
SendRequest(req, typeof(RspLogin));
}
public void OnPacketRspLogin(HttpResponseCode httpCode, Result result, RspLogin rsp)
{
if (httpCode == HttpResponseCode.OK && result.code == ResultCode.Success) {
DataManager.user = rsp.user;
Othello.Client.instance.UserToken = rsp.token;
}
}
65. Client.cs
•Server—>Client RPC 실행은 OnPacket 으로 시작하는 handler 들이 순
차적으로 실행
•동일한 Procedure 가 여럿 등록되어 실행 될 수 있음
•data 처리 부분과 UI 처리부분을 분리하기 위함
•항상 data 를 업데이트 Procedure 가 먼저 실행되어 데이터 업데이트
가 완료된 다음 UI 업데이트 Procedure 가 실행되어 UI 갱신
•UI 업데이트의 경우 여러 UI Component 에서 직접 UI 갱신
66. Client.cs
•Server—>Client RPC 실행은 OnPacket 으로 시작하는 handler 들이 순
차적으로 실행
•동일한 Procedure 가 여럿 등록되어 실행 될 수 있음
•data 처리 부분과 UI 처리부분을 분리하기 위함
•항상 data 를 업데이트 Procedure 가 먼저 실행되어 데이터 업데이트
가 완료된 다음 UI 업데이트 Procedure 가 실행되어 UI 갱신
•UI 업데이트의 경우 여러 UI Component 에서 직접 UI 갱신
Server Client
DataManager
GameScene
OthelloBoard, DashBoard
RspGameSync
67. Client.cs
•Procedure Call 을 할 때 Networking 을 크게 고민하지 않도록 구현
•HTTP Status Code == 200
•Procedure Call 에 return 에 해당하는 Response RPC 실행
•Procedure Call 로직내에서 검출되는 Error 는 resultCode 로 확인
•HTTP Status Code != 200
•Network Error 또는 Server Error
•Networking 담당 코드에서 Retry, Restart 옵션을 유저에게 제공
68. Client.cs
•Procedure Call 을 할 때 Networking 을 크게 고민하지 않도록 구현
•HTTP Status Code == 200
•Procedure Call 에 return 에 해당하는 Response RPC 실행
•Procedure Call 로직내에서 검출되는 Error 는 resultCode 로 확인
•HTTP Status Code != 200
•Network Error 또는 Server Error
•Networking 담당 코드에서 Retry, Restart 옵션을 유저에게 제공
실패했던 http request 를 그대로 다시 보냄
70. othello 완성
•Thrift, gRPC 포기 후 몬슈
리 방식을 수정하여 도입
•서버의 경우는 aiohttp 로
새로 작성(몬슈리는 flask)
•실제 코딩시간 40 시간 정
도
•AWS ECS 로 서비스 중
•Android 앱으로 빌드
(PlayStore “준곰오셀로”)
71. test_client.py
class Dispatcher:
...
def RspLogin(self, result, rsp):
if result.code == othello_pb2.ErrorUserNotRegistered:
client.rpc(othello_pb2.ReqRegister(
platform_type=othello_pb2.CUSTOM,
platform_token=client.user_platform_token,
name=client.user_name
))
elif result.code == othello_pb2.Success:
client.user = rsp.user
client.token = rsp.token
client.rpc(othello_pb2.ReqCreateGameRoom())
class Client:
...
def run(self):
self.rpc(othello_pb2.ReqLogin(platform_type=othello_pb2.CUSTOM,
platform_token=self.user_platform_token))
while len(self.rpc_queue) > 0:
remote_procedure = self.rpc_queue.pop(0)
status_code = self.__rpc(remote_procedure)
print('status_code:{}'.format(status_code))
if status_code != 200:
self.rpc_queue.insert(0, remote_procedure)
time.sleep(5)
•Python 으로 구현한 Othello
RPC test Client
•Response Service 의
procedure name 으로
handler 생성
•Exception 대신 Result Code
사용
•RPC 실제 실행은 main loop
에서 처리
72. othello python client
•github : https://goo.gl/Ws2qsn
•간단한 random play 를 하는 클라이언트
•현재 서버에서는 총 5개의 auto_client 가 대기중
•직접 protocol에 맞추어 클라이언트 개발을 해도
되고 auto_client 의 GamePut 부분을 수정하여
간단한 봇을 만들 수 있음
•파이콘 기간 중에 실행해보시는 분들께 추첨을 통
해 선물을 드립니다.
73. othello python client
•github : https://goo.gl/Ws2qsn
•간단한 random play 를 하는 클라이언트
•현재 서버에서는 총 5개의 auto_client 가 대기중
•직접 protocol에 맞추어 클라이언트 개발을 해도
되고 auto_client 의 GamePut 부분을 수정하여
간단한 봇을 만들 수 있음
•파이콘 기간 중에 실행해보시는 분들께 추첨을 통
해 선물을 드립니다.
75. 정리
•대부분의 RPC Framework 들은 Python 을 매우 잘 지원, 특히 Python
3.6 의 asyncio 용 Library 들이 많음.
•하지만, 게임의 특성에 따른 인증 절차 반영이 어려움
•하지만, 게임엔진의 가장 큰 축인 Unity의 C# 지원이 잘 안됨, 또한 생성
된 Client RPC 코드가 Unity 기준으로 가 Async 하지 않음
•위와 같은 이유로 기본적으로 RPC가 게임 서버/클라이언트에 잘 맞지는
않음
•다만, 각 RPC framework 마다 IDL, serializer 를 제공하니 이를 잘 이용
하면 직접 개발하는데 큰 도움이 될 수 있음
76. 생각해볼 것들
•gRPC 의 stream 처럼 HTTP/2 를 지원하면 GameSync 등은 필요 없지
않을까?
•FlatBuffers 의 벤치마킹 자료를 보면 성능이 우월한데 이런 serialized
data structure 를 더 살펴볼 필요는 있지 않을까?
•클라이언트에서 RPC return 을 처리 방식에 coroutine 을 통한 async
방식도 지원한다면?
77. 생각해볼 것들
•SMARTSTUDY 는 뭐하는 곳인가? 지금 뭐하고 있나?
오픈소스를 사랑하는 스마트스터디 기술본부는 Slack과 JIRA로 커뮤니케이션하고
GitHub Enterprise와 CircleCI Enterprise로 개발 및 통합 테스트 후에 Terraform으
로 관리되는 AWS 위에서 Docker 기반으로 서비스를 운영하며 DataDog으로 모니터
링을, 오류 추적은 Sentry에서 받으며 Unity와 Python으로 만든 몬스터 슈퍼리그는
글로벌 원 빌드로 게임을 즐기는 전 세계 친구들과 node.js채팅으로 대화를 나누고,
React로 만든 관리도구를 통해 5개 국어 / 2,500편의 핑크퐁 콘텐츠는 준-페타급 스
토리지 안에서 Transcoding 되어 YouTube 등에 올라가 누적 시청 수가 25억이 넘지
만, 이런 것들보다 더 중요한, 가장 중요한 건… 재미있게 같이 개발할 실력 있는 동료
를 스마트스터디는 항상 찾고 있다는 것!