Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Python 게임서버 안녕하십니까?
RPC Framework 편
스마트스터디 CTO 박준철
왜?
NDC 발표 (Python 게임서버 안녕하십니까? : 몬스터 슈퍼리그 게임 서버)
준비중에 사내 리뷰 과정에서 “너굴” 님의 질문으로부터 시작
너굴 : “게임 서버/클라 네트워킹 에서 RPC framework 를 ...
RPC 라는게 뭐길래?
게임 서버/클라에 쓸수 있나?
목표
• RPC framework 에 대한 정보 공유
• RPC framework 를 게임에 적용해보자
• 몬스터 슈퍼리그 에서 사용한 방식 공유
• 게임에 적합한 방식을 직접 만들어 보는 것
목차
• RPC
• Thrift , gRPC
• 몬스터 슈퍼리그 방식
• 게임에 맞게 RPC 만들기
• 정리 & 생각해볼 만한 것들
마스터, 준비 되었나요?
이제 시작합니다.
졸면 안되요!
RPC
• Remote Procedure Call
• wikipedia : In distributed computing, a remote procedure call (RPC)
is when a computer progr...
RPC
• Remote Procedure Call
• wikipedia : In distributed computing, a remote procedure call (RPC)
is when a computer progr...
RPC
1. Procedure name
2. Parameters
3. Networking
4. Protocol (message)
RPC
1. Procedure name
2. Parameters
3. Networking
4. Protocol (message)
•IDL (Interface Definition Language) 로
정의
•IDL 은 R...
RPC
1. Procedure name
2. Parameters
3. Networking
4. Protocol (message)
•Networking 방식의 차이 따라
Procedure Call 과 return 처리 방...
게임에서 RPC 선택
•IDL 정의를 서버/클라이언트가 코드 레벨에서 공유할 수 있나? ( 컴파일
타임에 오류 확인이 가능한 방식을 선호, 코드를 생성해주는 RPC
framework 의 IDL )
•클라이언트에서 asy...
Thrift 마스터, θrift 가 이 동네 

짱이라고 해요. 

같이 싸워 봐요.
Thrift (θrift)
•“scalable cross-language services development” 를 위해 Facebook
에서 개발, RPC framework 로 사용됨
•다양한 언어를 지원 ( http...
Thrift
•Server, Processor, Protocol, Transport 로 구성
•Thrift 를 통해서 code 생성을 하면 RPC Client 코드도 생성
•서버 / 클라이언트 의 가장 큰 차이는 당연하...
Thrift
•일단, Thrift 가 좋아보이니 이것으로 간단한 게임을 만들어보자.
•PT 준비가 산으로…
PT가 산으로 가고 있냥!!
산으로 가는 김에 잠시 소개 합니다
준곰
• 스마트스터디의 CTO 로 몬스터 슈퍼리그 개발에 참여했습니다.
• 넥슨에서 게임을 즐겁게 만드는 방법을 배웠습니다.
• 엔씨소프트에서 게임을 잘 만드는 방법을 배웠습니다.
• 네오위즈게임즈에서 게임을 처음부터 ...
다시 게임으로 돌아갑시다
Othello (오델로)
•Reversi(리버시) 라고도 부르는 보드게임
•두 명이 8x8 오델로 판 위에서 흑, 백 돌을 번갈아 놓으면서 진행
•처음에 판 가운데에 사각형으로 엇갈리게 배치된 돌 4개를 놓고 시작한다....
IDL
•Struct
• User, GameRoom
•Service
• User
• Login, Register
• GameRoom
• CreateGameRoom, JoinGameRoom, RandomJoin
• Gam...
서버
•Data Model
• User , SecurityData
• GameRoom
•Python 3.6.1
• SQLAlchemy
• mysqlclient
• asyncio
• aiothrift ( https://p...
클라이언트
•Intro Scene
• Register, Login
•Lobby Scene
• CreateGameRoom, JoinGameRoom, RandomJoin
•Game Scene
• Put, Exit, Game...
othello.thrift
namespace csharp othello
struct User {
1: optional i64 id=0;
2: optional string token="";
3: optional strin...
othello.thrift
namespace csharp othello
struct User {
1: optional i64 id=0;
2: optional string token="";
3: optional strin...
thrift generate code
$ thrift --gen py ./othello.thrift
$ thrift --gen csharp ./othello.thrift
$ find gen-py
gen-py
gen-py...
othello.server.py
import asyncio
import thriftpy
from aiothrift.server import create_server
# ...
othello = thriftpy.load(...
othello.server.py
class Dispatcher:
# ...
@db_transaction
def Login(self, platform_type, platform_token):
# ...
return use...
문제
•Server
•1 user 는 1개의 session 만 유지해야 하는 방법 필요
•중복 요청 방지를 위한 방법이 필요
•Client
•C# 으로 생성된 Client 코드는 TSocket 을 사용 (blocked-...
시도
•인증 전 후로 사용 가능한 Procedure 를 분리하고 인증 전에는 session
생성을 인증 후에는 session 체크를 하는 로직을 작성
•Processor 에 session token 생성, 체크 작성
•...
시도
•인증 전 후로 사용 가능한 Procedure 를 분리하고 인증 전에는 session
생성을 인증 후에는 session 체크를 하는 로직을 작성
•Processor 에 session token 생성, 체크 작성
•...
시도
•C# 의 생성된 service 코드를 coroutine 으로
•Thrift 가 생성한 코드는 async 처리가 불가능, async 한 처리를 위해서
Unity coroutine 코드가 필요
•TSocket 등 T...
시도
•C# 의 생성된 service 코드를 coroutine 으로
•Thrift 가 생성한 코드는 async 처리가 불가능, async 한 처리를 위해서
Unity coroutine 코드가 필요
•TSocket 등 T...
gRPC 마스터, 빠른 포기 다음에는
빠른 시도! 이번엔 gRPC와
불어 보시죠.
gRPC
• google 에서 개발
• Transport 로 HTTP/2 지원
• 양방향 streaming 지원
• IDL 로 google Protocol Buffers 사용
• 서버 클라이언트 모두 sync, asyn...
protobuf(Protocol Buffers)
•상세한 문서 ! ( https://developers.google.com/protocol-buffers/ )
•signed, unsigned 지원
•uint32, uin...
othello.proto
•unsigned 를 지원하므로 적절하게 이용
•Exception 이 없으므로 ResultCode 를 만들어 사용
•procedure 에 사용하는 parameter 와 return value 는...
othello.proto
•unsigned 를 지원하므로 적절하게 이용
•Exception 이 없으므로 ResultCode 를 만들어 사용
•procedure 에 사용하는 parameter 와 return value 는...
othello.proto
•procedure 에 사용하는 parameter 와 return value 는 별도로 정의
•return 값 에는 모두 Result 를 포함하도록 작성
message Result {
Resul...
othello.proto
•procedure 에 사용하는 parameter 와 return value 는 별도로 정의
•return 값 에는 모두 Result 를 포함하도록 작성
message Result {
Resul...
gRPC generate code
•pb2 , grpc 2개의 파일이 생성
•data class 들은 othello_pb2.py
•server, client class 들은 othello_pb2_grpc.py
$ pyt...
othello.server.py
•Thrift 와 별 차이 없음
•asyncio 잘 지원해주는 package 는 아직 없음
def run_forever(self):
self.server = grpc.server(futu...
othello.server.py
•Exception 을 지원하지 않고 있으므로 ResultCode 를 만들어 사용
class Dispatcher(othello_pb2_grpc.OthelloServicer):
@db_tr...
othello.server.py
•Exception 을 지원하지 않고 있으므로 ResultCode 를 만들어 사용
class Dispatcher(othello_pb2_grpc.OthelloServicer):
@db_tr...
문제
•Python Test Client 작동 확인. 문제는 역시나 Unity C# 지원 문제
•Unity Mono 는 .NET 4.5 가 아니라 .NET 2.0 지원 (Unity2017.1 버전
부터는 .NET 3.5...
문제
•Python Test Client 작동 확인. 문제는 역시나 Unity C# 지원 문제
•Unity Mono 는 .NET 4.5 가 아니라 .NET 2.0 지원 (Unity2017.1 버전
부터는 .NET 3.5...
오델로 하나 만들지 못하고 끝나나!
몬스터 슈퍼리그 방식 마스터, 과거를 돌아봐요.
그때 그 코드 그가 당신을
도와줄거에요.
몬스터 슈퍼리그
2017/04 NDC 발표 기준
몬스터 슈퍼리그
2017/04 NDC 발표 기준
Protocol Buffers
message MsgUserItem
{
optional fixed32 item_uid = 1;
optional uint32 item_count = 2;
}
enum MonsterStatTy...
Protocol Buffers
message Request {
optional uint32 protocolVersion = 1;
optional int32 protocolId = 2;
optional uint32 seq...
Protocol Buffers
•RPC return 역시 Server —> Client RPC 라고 보고 Response Service
생성, Response Service 에 모든 Procedure 를 등록
•인증에 ...
server.route.py
def route(ext):
def decorator(f):
succ = False
for field in request_pb2._REQUEST.fields:
if field.message_...
server.api.py
@route('ReqUserLogin')
def userLogin(reqUserLogin):
# ...
@app.route('/api', methods=['POST'])
def api():
re...
몬스터 슈퍼리그
•클라이언트에서 Synchronous Call 을 지원하지 않음
•Procedure Call 만 있을 뿐 return 은 Response 용 Procedure 가 클라
이언트에서 실행 되는 형태, 하지만...
몬스터 슈퍼리그
•클라이언트에서 Synchronous Call 을 지원하지 않음
•Procedure Call 만 있을 뿐 return 은 Response 용 Procedure 가 클라
이언트에서 실행 되는 형태, 하지만...
게임에 맞게 RPC 만들기
마스터, 그냥 대충해.
시도
•게임 개발에 필요한 수준으로 직접 만들어보자
•개발 난이도가 높은 IDL 과 message 의 serializer 는 기존 것을 선택하자
•서버는 http server 를 사용하고 RPC 설계는 몬스터 슈퍼리그 ...
선택
•IDL : Protocol Buffers (serializer 포함, service 정의는 사용하지 않음)
•Procedure 는 IDL 에 정의한 message 를 활용한다
•ReqLogin, RspLogin
...
othello.proto
•Procedure 를 protobuf IDL 의
message 로 정의
•Client —> Server 의 Procedure 는
Request 라는 message 내에 모두
등록. (Reque...
othello.proto
•Procedure 를 protobuf IDL 의
message 로 정의
•Client —> Server 의 Procedure 는
Request 라는 message 내에 모두
등록. (Reque...
othello.proto
•RPC return 값은 Server —> Client
Procedure Call 로 정의하고 Response
message 를 만들어 Procedure 를 모두
등록
•서버 기준에서 한번에 ...
othello.server.py
•aiohttp 는 +_+b 좋음
•health 는 ELB target group 의 health check 용
•api, route, handle 는 몬스터 슈퍼리그의 api 코드 참고...
Client.cs
•Procedure Call (SendPacket) 하고 wait 하지 않음
•Server—>Client RPC 실행은 OnPacket 으로 시작하는 handler 들이 순
차적으로 실행
public ...
Client.cs
•Server—>Client RPC 실행은 OnPacket 으로 시작하는 handler 들이 순
차적으로 실행
•동일한 Procedure 가 여럿 등록되어 실행 될 수 있음
•data 처리 부분과 UI...
Client.cs
•Server—>Client RPC 실행은 OnPacket 으로 시작하는 handler 들이 순
차적으로 실행
•동일한 Procedure 가 여럿 등록되어 실행 될 수 있음
•data 처리 부분과 UI...
Client.cs
•Procedure Call 을 할 때 Networking 을 크게 고민하지 않도록 구현
•HTTP Status Code == 200
•Procedure Call 에 return 에 해당하는 Respo...
Client.cs
•Procedure Call 을 할 때 Networking 을 크게 고민하지 않도록 구현
•HTTP Status Code == 200
•Procedure Call 에 return 에 해당하는 Respo...
othello 완성
$ python3 manage.py runserver --port 14500 local.cfg
init session
======== Running on http://127.0.0.1:14500 ==...
othello 완성
•Thrift, gRPC 포기 후 몬슈
리 방식을 수정하여 도입
•서버의 경우는 aiohttp 로
새로 작성(몬슈리는 flask)
•실제 코딩시간 40 시간 정
도
•AWS ECS 로 서비스 중
•A...
test_client.py
class Dispatcher:
...
def RspLogin(self, result, rsp):
if result.code == othello_pb2.ErrorUserNotRegistered...
othello python client
•github : https://goo.gl/Ws2qsn
•간단한 random play 를 하는 클라이언트
•현재 서버에서는 총 5개의 auto_client 가 대기중
•직접 pr...
othello python client
•github : https://goo.gl/Ws2qsn
•간단한 random play 를 하는 클라이언트
•현재 서버에서는 총 5개의 auto_client 가 대기중
•직접 pr...
정리 & 생각해볼 것들
이제 얼마 남지 않았어!
정리
•대부분의 RPC Framework 들은 Python 을 매우 잘 지원, 특히 Python
3.6 의 asyncio 용 Library 들이 많음.
•하지만, 게임의 특성에 따른 인증 절차 반영이 어려움
•하지만, ...
생각해볼 것들
•gRPC 의 stream 처럼 HTTP/2 를 지원하면 GameSync 등은 필요 없지
않을까?
•FlatBuffers 의 벤치마킹 자료를 보면 성능이 우월한데 이런 serialized
data stru...
생각해볼 것들
•SMARTSTUDY 는 뭐하는 곳인가? 지금 뭐하고 있나?
오픈소스를 사랑하는 스마트스터디 기술본부는 Slack과 JIRA로 커뮤니케이션하고
GitHub Enterprise와 CircleCI Enterp...
감사합니다
Upcoming SlideShare
Loading in …5
×

Python 게임서버 안녕하십니까 : RPC framework 편

2,067 views

Published on

NDC Python 게임서버 안녕하십니까? : 몬스터 슈퍼리그 게임 서버 편의 후속으로 기획된 발표입니다. 사내 준비 도중 "너굴" 님의 질문에서 시작되었습니다.

이 발표는 잘 알려진 RPC Framework 인 Thrift, gRPC를 살펴보고 예시로 오델로 게임을 만들어보면서 기존 RPC framework 들이 게임의 서버/클라 구조에 잘 어울리지는 살펴보고 왜 몬스터 슈퍼리그에서 그런 선택을 했는지 살펴봅니다.

그리고 게임에 맞게 RPC 를 설계하고 이를 이용하여 온라인 오델로 게임을 완성해봅니다.

Published in: Technology

Python 게임서버 안녕하십니까 : RPC framework 편

  1. 1. Python 게임서버 안녕하십니까? RPC Framework 편 스마트스터디 CTO 박준철
  2. 2. 왜? NDC 발표 (Python 게임서버 안녕하십니까? : 몬스터 슈퍼리그 게임 서버) 준비중에 사내 리뷰 과정에서 “너굴” 님의 질문으로부터 시작 너굴 : “게임 서버/클라 네트워킹 에서 RPC framework 를 
 사용하지 않고 직접 구현하신 이유가 있나요?” 준곰 : “어쩌고 저쩌고… 그래서 어쩌고저쩌고” 너굴 : “네…”
  3. 3. RPC 라는게 뭐길래? 게임 서버/클라에 쓸수 있나?
  4. 4. 목표 • RPC framework 에 대한 정보 공유 • RPC framework 를 게임에 적용해보자 • 몬스터 슈퍼리그 에서 사용한 방식 공유 • 게임에 적합한 방식을 직접 만들어 보는 것
  5. 5. 목차 • RPC • Thrift , gRPC • 몬스터 슈퍼리그 방식 • 게임에 맞게 RPC 만들기 • 정리 & 생각해볼 만한 것들 마스터, 준비 되었나요? 이제 시작합니다. 졸면 안되요!
  6. 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. 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. 네트워크 상태나 콜 방식을 신경쓰지 않고 프로그래머가 원격의 함수를 실행하는 것
  8. 8. RPC 1. Procedure name 2. Parameters 3. Networking 4. Protocol (message)
  9. 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. 10. RPC 1. Procedure name 2. Parameters 3. Networking 4. Protocol (message) •Networking 방식의 차이 따라 Procedure Call 과 return 처리 방식이 달 라짐 •message 의 (de)serializer 차이에 따라 Protocol 의 성능이나 보안의 차이가 존재
  11. 11. 게임에서 RPC 선택 •IDL 정의를 서버/클라이언트가 코드 레벨에서 공유할 수 있나? ( 컴파일 타임에 오류 확인이 가능한 방식을 선호, 코드를 생성해주는 RPC framework 의 IDL ) •클라이언트에서 async call 을 지원해야 하며 return 의 형태나 return 의 처리 과정에 개입할 수 있나? •Unity (.Net 2.0, .Net 3.5, C# 4) , C++ 지원하나? •json, xml을 사용하지 않고 빠른 자체 message protocol 지원하는가?
  12. 12. Thrift 마스터, θrift 가 이 동네 
 짱이라고 해요. 
 같이 싸워 봐요.
  13. 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. 14. Thrift •Server, Processor, Protocol, Transport 로 구성 •Thrift 를 통해서 code 생성을 하면 RPC Client 코드도 생성 •서버 / 클라이언트 의 가장 큰 차이는 당연하게도 Processor 유무 •Protocol, Transport 는 각각 Serialization 과 Networking 을 담당
  15. 15. Thrift •일단, Thrift 가 좋아보이니 이것으로 간단한 게임을 만들어보자. •PT 준비가 산으로… PT가 산으로 가고 있냥!!
  16. 16. 산으로 가는 김에 잠시 소개 합니다
  17. 17. 준곰 • 스마트스터디의 CTO 로 몬스터 슈퍼리그 개발에 참여했습니다. • 넥슨에서 게임을 즐겁게 만드는 방법을 배웠습니다. • 엔씨소프트에서 게임을 잘 만드는 방법을 배웠습니다. • 네오위즈게임즈에서 게임을 처음부터 만들고 끝까지 완성하는 방법을 배웠습니다. • 스마트스터디에서는 게임을 만들어 성공하는 방법을 배웠습니 다.
  18. 18. 다시 게임으로 돌아갑시다
  19. 19. Othello (오델로) •Reversi(리버시) 라고도 부르는 보드게임 •두 명이 8x8 오델로 판 위에서 흑, 백 돌을 번갈아 놓으면서 진행 •처음에 판 가운데에 사각형으로 엇갈리게 배치된 돌 4개를 놓고 시작한다. •돌은 반드시 상대방 돌을 양쪽에서 포위하여 뒤집을 수 있는 곳에 놓아야 한다. •돌을 뒤집을 곳이 없는 경우에는 차례가 자동적으로 상대방에게 넘어가게 된다. •아래와 같은 조건에 의해 양쪽 모두 더 이상 돌을 놓을 수 없게 되면 게임이 끝 나게 된다. • 64개의 돌 모두가 판에 가득 찬 경우 (가장 일반적) • 어느 한 쪽이 돌을 모두 뒤집은 경우 • 한 차례에 양 쪽 모두 서로 차례를 넘겨야 하는 경우 •게임이 끝났을 때 돌이 많이 있는 플레이어가 승자가 된다. 만일 돌의 개수가 같 을 경우는 무승부가 된다. wikipedia
  20. 20. IDL •Struct • User, GameRoom •Service • User • Login, Register • GameRoom • CreateGameRoom, JoinGameRoom, RandomJoin • Game • Put, Exit, GameOver, Sync
  21. 21. 서버 •Data Model • User , SecurityData • GameRoom •Python 3.6.1 • SQLAlchemy • mysqlclient • asyncio • aiothrift ( https://pypi.python.org/pypi/aiothrift )
  22. 22. 클라이언트 •Intro Scene • Register, Login •Lobby Scene • CreateGameRoom, JoinGameRoom, RandomJoin •Game Scene • Put, Exit, GameOver, Sync
  23. 23. othello.thrift namespace csharp othello struct User { 1: optional i64 id=0; 2: optional string token=""; 3: optional string name=""; 4: optional i32 level=1; 5: optional i32 exp=0; 6: optional i32 win=0; 7: optional i32 lose=0; 8: optional i32 gold=0; } enum PlatformType { CUSTOM = 1, GAME_CENTER = 2, GOOGLE_PLAY = 3, FACEBOOK = 4, } // exceptions exception ErrorUserNotRegistered {} exception ErrorUserNameAlreadyExists {} exception ErrorUserAlreadyExists {} exception ErrorUserInvalidName {} exception ErrorSystem { 1: optional i32 code; 2: optional string message; } // services service OthelloService { User Login(1:PlatformType platform_type, 2:string platform_token) throws (1: ErrorUserNotRegistered errUserNotRegistered), User Register(1:PlatformType platform_type, 2:string platform_token, 3:string name) throws (1: ErrorUserNameAlreadyExists errUserNameAlreadyExists, 2: ErrorUserInvalidName errUserInvalidName, 3: ErrorSystem errSystem) }
  24. 24. othello.thrift namespace csharp othello struct User { 1: optional i64 id=0; 2: optional string token=""; 3: optional string name=""; 4: optional i32 level=1; 5: optional i32 exp=0; 6: optional i32 win=0; 7: optional i32 lose=0; 8: optional i32 gold=0; } enum PlatformType { CUSTOM = 1, GAME_CENTER = 2, GOOGLE_PLAY = 3, FACEBOOK = 4, } // exceptions exception ErrorUserNotRegistered {} exception ErrorUserNameAlreadyExists {} exception ErrorUserAlreadyExists {} exception ErrorUserInvalidName {} exception ErrorSystem { 1: optional i32 code; 2: optional string message; } // services service OthelloService { User Login(1:PlatformType platform_type, 2:string platform_token) throws (1: ErrorUserNotRegistered errUserNotRegistered), User Register(1:PlatformType platform_type, 2:string platform_token, 3:string name) throws (1: ErrorUserNameAlreadyExists errUserNameAlreadyExists, 2: ErrorUserInvalidName errUserInvalidName, 3: ErrorSystem errSystem) }
  25. 25. thrift generate code $ thrift --gen py ./othello.thrift $ thrift --gen csharp ./othello.thrift $ find gen-py gen-py gen-py/__init__.py gen-py/othello gen-py/othello/__init__.py gen-py/othello/constants.py gen-py/othello/OthelloService-remote gen-py/othello/OthelloService.py gen-py/othello/ttypes.py $ find gen-csharp gen-csharp gen-csharp/othello gen-csharp/othello/thrift gen-csharp/othello/thrift/ErrorSystem.cs gen-csharp/othello/thrift/ErrorUserAlreadyExists.cs gen-csharp/othello/thrift/ErrorUserInvalidName.cs gen-csharp/othello/thrift/ErrorUserNameAlreadyExists.cs gen-csharp/othello/thrift/ErrorUserNotRegistered.cs gen-csharp/othello/thrift/OthelloService.cs gen-csharp/othello/thrift/PlatformType.cs gen-csharp/othello/thrift/User.cs •thrift --gen [language] [file] •gen-[language] 폴더에 code 가 생 성
  26. 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. 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. 28. 문제 •Server •1 user 는 1개의 session 만 유지해야 하는 방법 필요 •중복 요청 방지를 위한 방법이 필요 •Client •C# 으로 생성된 Client 코드는 TSocket 을 사용 (blocked-io) 이는 synchronous
  29. 29. 시도 •인증 전 후로 사용 가능한 Procedure 를 분리하고 인증 전에는 session 생성을 인증 후에는 session 체크를 하는 로직을 작성 •Processor 에 session token 생성, 체크 작성 •Protocol 에 session token 기본 포함, 생성된 코드에서 매번 session token 을 넣지 않도록 작성
  30. 30. 시도 •인증 전 후로 사용 가능한 Procedure 를 분리하고 인증 전에는 session 생성을 인증 후에는 session 체크를 하는 로직을 작성 •Processor 에 session token 생성, 체크 작성 •Protocol 에 session token 기본 포함, 생성된 코드에서 매번 session token 을 넣지 않도록 작성 빠른 포기!!! •IDL을 기준으로 코드 생성이 되므로 IDL 에 없는 상태에서 이를 반영하기 위해서는 Protocol 을 수정할 필요가 있음 •Protocol 에서의 인증 절차 등이 필요함
  31. 31. 시도 •C# 의 생성된 service 코드를 coroutine 으로 •Thrift 가 생성한 코드는 async 처리가 불가능, async 한 처리를 위해서 Unity coroutine 코드가 필요 •TSocket 등 Transport 도 coroutine 으로 작성되어 있지 않음 •생성된 코드기준으로 async, await 등은 C# 5 이상 필요한 만큼 async call 을 사용할 수 없음
  32. 32. 시도 •C# 의 생성된 service 코드를 coroutine 으로 •Thrift 가 생성한 코드는 async 처리가 불가능, async 한 처리를 위해서 Unity coroutine 코드가 필요 •TSocket 등 Transport 도 coroutine 으로 작성되어 있지 않음 •생성된 코드기준으로 async, await 등은 C# 5 이상 필요한 만큼 async call 을 사용할 수 없음 빠른 포기!!! •TSocket, Transport 를 coroutine 으로 새로 작성해야 함
  33. 33. gRPC 마스터, 빠른 포기 다음에는 빠른 시도! 이번엔 gRPC와 불어 보시죠.
  34. 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. 35. protobuf(Protocol Buffers) •상세한 문서 ! ( https://developers.google.com/protocol-buffers/ ) •signed, unsigned 지원 •uint32, uint64 •nested type 지원 •message { message { enum { } } } •repeated (list), map container 지원
  36. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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 를 써보려고 한 건 아닌데 빠른 포기!!!
  46. 46. 오델로 하나 만들지 못하고 끝나나!
  47. 47. 몬스터 슈퍼리그 방식 마스터, 과거를 돌아봐요. 그때 그 코드 그가 당신을 도와줄거에요.
  48. 48. 몬스터 슈퍼리그 2017/04 NDC 발표 기준
  49. 49. 몬스터 슈퍼리그 2017/04 NDC 발표 기준
  50. 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. 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. 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. 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 기준으로 
 실행
  54. 54. server.api.py @route('ReqUserLogin') def userLogin(reqUserLogin): # ... @app.route('/api', methods=['POST']) def api(): req = request_pb2.Request.FromString(reqBody) # ... db_begin() try: rsp = handle(req) db_commit() except: db_rollback() finally: db_end() return rsp •실제 몬슈리에서 Request 를 처리하는 기본 로직
  55. 55. 몬스터 슈퍼리그 •클라이언트에서 Synchronous Call 을 지원하지 않음 •Procedure Call 만 있을 뿐 return 은 Response 용 Procedure 가 클라 이언트에서 실행 되는 형태, 하지만 양방향통신 이라고 할 수는 없음 •Connection 연결 유지가 필요없는 구조, 서버 클라 모두 부담이 적음 •Protobuf 사용으로 인한 serialize , deserialize 비용이 낮음 •게임 데이터 를 Protobuf 로 생성한 struct 사용
  56. 56. 몬스터 슈퍼리그 •클라이언트에서 Synchronous Call 을 지원하지 않음 •Procedure Call 만 있을 뿐 return 은 Response 용 Procedure 가 클라 이언트에서 실행 되는 형태, 하지만 양방향통신 이라고 할 수는 없음 •Connection 연결 유지가 필요없는 구조, 서버 클라 모두 부담이 적음 •Protobuf 사용으로 인한 serialize , deserialize 비용이 낮음 •게임 데이터 를 Protobuf 로 생성한 struct 사용 도전!!!
  57. 57. 게임에 맞게 RPC 만들기 마스터, 그냥 대충해.
  58. 58. 시도 •게임 개발에 필요한 수준으로 직접 만들어보자 •개발 난이도가 높은 IDL 과 message 의 serializer 는 기존 것을 선택하자 •서버는 http server 를 사용하고 RPC 설계는 몬스터 슈퍼리그 방식을 사 용하자
  59. 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. 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. 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. 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. 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. 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. 65. Client.cs •Server—>Client RPC 실행은 OnPacket 으로 시작하는 handler 들이 순 차적으로 실행 •동일한 Procedure 가 여럿 등록되어 실행 될 수 있음 •data 처리 부분과 UI 처리부분을 분리하기 위함 •항상 data 를 업데이트 Procedure 가 먼저 실행되어 데이터 업데이트 가 완료된 다음 UI 업데이트 Procedure 가 실행되어 UI 갱신 •UI 업데이트의 경우 여러 UI Component 에서 직접 UI 갱신
  66. 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. 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. 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 를 그대로 다시 보냄
  69. 69. othello 완성 $ python3 manage.py runserver --port 14500 local.cfg init session ======== Running on http://127.0.0.1:14500 ======== (Press CTRL+C to quit) begin_session protocolVersion: 1 protocolId: 100 seqNo: 1 loginReq { platform_type: CUSTOM platform_token: "1c8daa46-74ec-4f7f-9a6a-42dc9ecb9762" } REQUEST : ReqLogin platform_type: CUSTOM platform_token: "1c8daa46-74ec-4f7f-9a6a-42dc9ecb9762" commit end_session reqSeqNo: 1 responses { protocolVersion: 1 protocolId: 100 result { code: Success } loginRsp { user { id: 5 name: "joongom3" level: 1 exp: 0 win: 0 lose: 0 gold: 0 } platform_token: "1c8daa46-74ec-4f7f-9a6a-42dc9ecb9762" token: "d68a4478-79e3-11e7-bf27-a45e60f1ced1" } }
  70. 70. othello 완성 •Thrift, gRPC 포기 후 몬슈 리 방식을 수정하여 도입 •서버의 경우는 aiohttp 로 새로 작성(몬슈리는 flask) •실제 코딩시간 40 시간 정 도 •AWS ECS 로 서비스 중 •Android 앱으로 빌드 (PlayStore “준곰오셀로”)
  71. 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. 72. othello python client •github : https://goo.gl/Ws2qsn •간단한 random play 를 하는 클라이언트 •현재 서버에서는 총 5개의 auto_client 가 대기중 •직접 protocol에 맞추어 클라이언트 개발을 해도 되고 auto_client 의 GamePut 부분을 수정하여 간단한 봇을 만들 수 있음 •파이콘 기간 중에 실행해보시는 분들께 추첨을 통 해 선물을 드립니다.
  73. 73. othello python client •github : https://goo.gl/Ws2qsn •간단한 random play 를 하는 클라이언트 •현재 서버에서는 총 5개의 auto_client 가 대기중 •직접 protocol에 맞추어 클라이언트 개발을 해도 되고 auto_client 의 GamePut 부분을 수정하여 간단한 봇을 만들 수 있음 •파이콘 기간 중에 실행해보시는 분들께 추첨을 통 해 선물을 드립니다.
  74. 74. 정리 & 생각해볼 것들 이제 얼마 남지 않았어!
  75. 75. 정리 •대부분의 RPC Framework 들은 Python 을 매우 잘 지원, 특히 Python 3.6 의 asyncio 용 Library 들이 많음. •하지만, 게임의 특성에 따른 인증 절차 반영이 어려움 •하지만, 게임엔진의 가장 큰 축인 Unity의 C# 지원이 잘 안됨, 또한 생성 된 Client RPC 코드가 Unity 기준으로 가 Async 하지 않음 •위와 같은 이유로 기본적으로 RPC가 게임 서버/클라이언트에 잘 맞지는 않음 •다만, 각 RPC framework 마다 IDL, serializer 를 제공하니 이를 잘 이용 하면 직접 개발하는데 큰 도움이 될 수 있음
  76. 76. 생각해볼 것들 •gRPC 의 stream 처럼 HTTP/2 를 지원하면 GameSync 등은 필요 없지 않을까? •FlatBuffers 의 벤치마킹 자료를 보면 성능이 우월한데 이런 serialized data structure 를 더 살펴볼 필요는 있지 않을까? •클라이언트에서 RPC return 을 처리 방식에 coroutine 을 통한 async 방식도 지원한다면?
  77. 77. 생각해볼 것들 •SMARTSTUDY 는 뭐하는 곳인가? 지금 뭐하고 있나? 오픈소스를 사랑하는 스마트스터디 기술본부는 Slack과 JIRA로 커뮤니케이션하고 GitHub Enterprise와 CircleCI Enterprise로 개발 및 통합 테스트 후에 Terraform으 로 관리되는 AWS 위에서 Docker 기반으로 서비스를 운영하며 DataDog으로 모니터 링을, 오류 추적은 Sentry에서 받으며 Unity와 Python으로 만든 몬스터 슈퍼리그는 글로벌 원 빌드로 게임을 즐기는 전 세계 친구들과 node.js채팅으로 대화를 나누고, React로 만든 관리도구를 통해 5개 국어 / 2,500편의 핑크퐁 콘텐츠는 준-페타급 스 토리지 안에서 Transcoding 되어 YouTube 등에 올라가 누적 시청 수가 25억이 넘지 만, 이런 것들보다 더 중요한, 가장 중요한 건… 재미있게 같이 개발할 실력 있는 동료 를 스마트스터디는 항상 찾고 있다는 것!
  78. 78. 감사합니다

×