블로그 이미지
Every unexpected event is a path to learning for you.

카테고리

분류 전체보기 (2738)
Unity3D (817)
Programming (475)
Server (33)
Unreal (4)
Gamebryo (56)
Tip & Tech (228)
협업 (58)
3DS Max (3)
Game (12)
Utility (136)
Etc (96)
Link (32)
Portfolio (19)
Subject (90)
iOS,OSX (53)
Android (14)
Linux (5)
잉여 프로젝트 (2)
게임이야기 (3)
Memories (20)
Interest (38)
Thinking (38)
한글 (30)
PaperCraft (5)
Animation (408)
Wallpaper (2)
재테크 (18)
Exercise (3)
나만의 맛집 (3)
냥이 (10)
육아 (16)
Total
Today
Yesterday
05-08 00:11

[링크] https://brunch.co.kr/@chris-song/16

 

.NET Core 게임서버 개발 1편 (Win)

ASP.NET Core로 30분만에 게임서버를! | 맥 버전 가이드는 아래 글에서 확인하세요 ^^ https://brunch.co.kr/@chris-song/15 제가 유니티 책을 쓰면서 가장 관심이 많았던 주제는 바로, "어떻게 하면 유니티 개발

brunch.co.kr

 

반응형
Posted by blueasa
, |

[출처] http://sjava.net/?p=416



이 내용은 소프트웨어 엔지니어이자 기업가인 마틴 클레프만(Martin Kleppmann)의 블로그에 있는(http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html) 있는 내용이다. 이 내용의 번역은 마틴 클레프만의 동의를 얻어서 진행했다.

에이브로(Avro), 프로토콜 버퍼(Protocol Buffers) 그리고 스리프트(Thrift)의 스키마 변경(evolution).
자 여러분은 파일로 저장하거나 네트워크로 전송하려는 데이터를 가지고 있다. 그리고 변경(진화)의 여러 단계를 거치면서 스스로 찾아갈 수도 있다:

  1. 자바 직렬화(Java serialization), 루비 마샬(marshal)과 파이썬 픽클(pickle)같은 프로그래밍 언어가 지원하는 직렬화를 사용하거나, 자신의 포맷을 개발해서 사용할 수도 있다.
  2. 다음에, 단일 프로그래밍 언어에 종속된다는 것을 알게 될 것이고, 그래서, 더 넓게 지원하도록 JSON(XML) 같이 언어에 독립적인(language-agnostic) 포맷을 사용하도록 변경한다.
  3. 그다음으로, “JSON은 너무 장황(verbose)하고, 파싱이 느리다” 라고 판단하고, 부동 소수(floating point)와 정수(integers)를 구분할 수 없는 것에 실망하고, 유니코드 문자열뿐 아니라 바이너리 문자열도 필요하다고 생각한다. 그래서, JSON의 일종으로 바이너리(MessagePackBSONUniversal Binary JSONBJSONBinary JSON Serialization) 포맷을 만든다.
  4. 또 다음으로, 사람들이 일관성 없는 타입을 사용해서, 객체에 여러 임의(random)의 필드를 넣고 있는 것을 발견하고, 스키마(schema)나 문서(documentation)에 대해서 매우 감사할 것이다. 아마도 당신 역시, 정적 타입 프로그래밍 언어를 사용하고 있고, 스키마에서 모델 클래스를 생성하길 원할 것이다. 그리고 바이너리 JSON은 계속해서 필드 이름을 저장하기에 실제로는 간결하지 않다는 것을 알게 될 것이다; 만약 스키마가 있다면, 객체 필드 이름을 저장하지 않아도 되고, 더 공간(bytes)을 절약할 수 있다.

만약, 4.의 단계를 고민한다면, 일반적으로 선택할 수 있는 옵션은 스리프트(Thrift), 프로토콜 버퍼(Protocol Buffers)와 에이브로(Avro)가 있다. 이 세 가지는 효율적이고, 스키마를 이용한 데이터의 언어에 독립적인(cross-language) 직렬화와 자바 진영(folks)을 위한 코드 생성도 제공한다.

이것들에 대한 비교는 이미(Protocol Buffers, Avro, Thrift & MessagePackThrift vs Protocol Bufffers vs JSONComparison of Protobuff, Thrift, Avro, etc) 확인할 수 있다. 하지만 많은 글이 일상적으로 보이지만, 중요한 세부내용을 간과하고 있다: 스키마를 변경하면, 어떻게 될까?

실제로 데이터는 항상 유동적이다. 완성된 스키마를 가지고 있다고 생각하는 순간, 누군가 예상치 않은 사례(use case)를 가지고 올 것이고, “단지 빠르게 필드를 추가”하길 원한다. 다행스럽게도, 스리프트, 프로토콜 버퍼 그리고 에이브로 모두 스키마 변경(schema evolution)를 지원한다: 스키마를 변경할 수 있는 동시에, 스키마의 다양한 버전에 대한 프로듀서(producers)와 컨슈머(consumers)를 사용할 수 있어서, 이전 버전의 스키마를 계속 사용할 수 있다. 이것으로, 호환성 걱정 없이, 서로 다른 시간에, 시스템의 각 컴포넌트를 독립적으로 갱신할 수 있게 해서, 매우 큰 시스템을 다룰 때, 매우 중요한 기능이다.

이것이 이 글의 주제이다. 실제로 프로토콜 버퍼, 에이브로 그리고 스리프트가 데이터를 데이터를 바이트로 인코딩하는 방법을 살펴본다. 이 내용은 개별 프레임웍이 스키마 변경을 처리하는 방법을 이해하는데 도움이 될 것이다. 이 프레임웍들이 만들어진 설계의 선택에 대해서 관심과 비교를 통해서 더 좋은 엔지니어가 될 수 있을 것이다.

예로, Person을 나타내는 작은 객체를 사용할 것이고, JSON으로 아래처럼 기술할 수 있다:

01
02
03
04
05
{
  “userName”: “Martin”,
  “favouriteNumber”: 1337,
  “interests”: [“daydreaming”, “hacking”]
}

이 JSON 인코딩이 기준이 될 수 있다. 모든 공백을 지우면, 82바이트다.

프로토콜 버퍼
Person 객체에 대한 프로토콜 버퍼의 스키마는 아래와 같다:

01
02
03
04
05
message Person {
    required string user_name        = 1;
    optional int64  favourite_number = 2;
    repeated string interests        = 3;
}

이 스키마로 데이터를 인코딩하면, 아래와 같이 33 바이트를 사용한다.

바이너리 형태가 어떻게 바이트 단위로 구성되는지 정확하게 살펴봐라. Person 레코드는 단지 필드의 연속이다. 각 필드는 태그 넘버(위의 스키마에서 1, 2, 3)를 지정하는 1 바이트로 시작하고, 다음으로 필드의 타입이다. 필드의 첫 바이트를 타입인 문자열로 지정한다면, 다음으로 문자열의 바이트 수와, UTF-8로 인코딩된 문자열이 따라온다. 첫 바이트를 정수로 지정한다면, 가변 길이로 인코딩된 수(값)가 나온다. 배열 타입은 없지만, 다중 값(multi-valued) 필드를 나타내기 위해서 태그 번호를 여러 번 사용할 수 있다.

이 인코딩 형태는 스키마 변화에 대해서 아래의 결과를 가진다:

  • 선택적(optional), 필수적(required) 그리고 반복(repeated)되는 필드(태그 번호가 나타나는 횟수를 제외하고)의 인코딩 차이는 없다. 이것은 선택적에서 반복으로 변경할 수 있다는 것이고, 반대로 변경해도 같다(파서가 예상한 선택적 필드에, 한 레코드에 같은 태그 번호가 여러 번 보이는 경우, 마지막 값을 제외하고 모두 삭제한다.)는 것이다. 필수적 필드는 추가로 유효성 검사를 한다. 그래서 스키마를 바꾼다면, 런타임 에러(메시지를 보내는 사람은 선택적이라고 생각했지만, 받는 쪽이 필수라고 생각하는)의 위험이 있다.
  • 빈 값의 선택적 필드 또는 0 값이 반복되는 필드는 인코딩된 데이터에 전혀 나타나지 않는다-다만 태그 번호만 나온다. 따라서 스키마에서 이런 종류의 필드는 삭제하는 것이 안전하다. 하지만 아직도 삭제한 필드의 태그를 사용해서 저장된 데이터가 있을 수 있기에, 앞으로는 다른 필드에서 태그 번호를 재사용하면 안 된다.
  • 태그 번호를 제공할 수 있다면, 레코드에 필드를 추가할 수 있다. 프로토콜 버퍼 파서가 스키마에 정의되지 않은 태그 번호를 만나면, 그 필드가 호출됐는지 알 수가 없다. 하지만 필드의 첫 바이트에 3-bit 타입코드가 포함되어 있어서, 대략 어떤 타입인지 알고 있다. 이것은 파서가 정확히 필드를 해석할 순 없지만, 이것으로 레코드에서 다음 필드를 찾기 위해, 몇 개의 바이트를 생략해야 하는지 알 수 있다.
  • 직렬화된 바이너리에 필드 이름이 없기에, 필드 이름을 바꿀 수 있지만, 태그 번호는 바꿀 수 없다.

개별 필드를 나타내기 위해서 태그 번호를 사용하는 방식은 간단하고 효과적이다. 하지만 이 방식이 유일하지 않다는 것은 금방 알 수 있을 것이다.

에이브로
에이브로 스키마는 2가지 방법으로 사용할 수 있고, 한 가지 방법은 JSON 포맷을 사용하는 것이고:

01
02
03
04
05
06
07
08
09
{
  “type”: “record”,
  “name”: “Person”,
  “fields”: [
      {“name”: “userName”,        “type”: “string”},
      {“name”: “favouriteNumber”, “type”: [“null”, “long”]},
      {“name”: “interests”,       “type”: {“type”: “array”, “items”: “string”}}
  ]
}

다른 방법은 IDL을 사용하는 것이다:

01
02
03
04
05
record Person {
    string               userName;
    union { null, long } favouriteNumber;
    array<string>        interests;
}

스키마에 태그 번호가 없는 것을 확인해라! 그럼 어떻게 동작할까?
아래는 32바이트로 인코딩(http://avro.apache.org/docs/current/spec.html)된 같은 예제 데이터다.

문자열 앞은 길이(length)이고 다음으로 UTF-8 바이트지만, 바이트스트림(bytestream)은 그것이 문자열이라고 알려주지 않는다. 이것(바이너리 데이터)은 단지 가변 길이 정수가 될 수 있지만, 완전히 다를 수 있다. 이 바이너리 데이터를 파싱할 수 있는 유일한 방법은 스키마와 같이 읽는 것이고, 스키마는 다음에 나오는 타입이 무엇인지 알려준다.
데이터의 작성자(writer)가 사용한 스키마의 정확히 같은 버전이 필요하다. 다른 스키마를 가지고 있다면, 파서는 바이너리 데이터의 헤드(head)나 테일(tail)을 만들 수 없게 될 것이다.

그런데 에이브로는 어떻게 스키마 변화를 지원할까? 비록 작성자의 스키마로 작성된 데이터와 정확한 스키마를 알 필요가 있지만, 컨슈머(consumer)가 기대하는(구독자(reader)의 스키마) 스키마가 같아야 한다는 것은 아니다. 실제로, 에이브로 파서에 다른 두 개의 스키마를 제공할 수 있고, 파서는 작성자 스키마를 구독자 스키마로 변환하기 위해서 해결 규칙(resolution rules)을 사용한다.

이것은 스키마 변화에 대해 몇 가지 흥미로운 결과를 가지게 한다:

  • 에이브로 인코딩은 어떤 필드가 다음인지 알려주는 지시자(indicator)가 없다: 단지, 스키마에 나타나는 차례대로 인코딩한다. 파서는 필드가 생략되었다는 것을 알 방법이 없기에, 에이브로에는 선택적인 필드가 없다. 대신, 값을 제외하길 원한다면, 위에서 유니온(union { null, long })과 같은 타입을 사용할 수 있다. 유니온 타입을 사용하기 위해서, 파서에게 1바이트로 인코딩해서 알려준다. null 타입(간단히 0바이트로 인코딩됨)으로 유니온을 만들어서, 필드를 선택적으로 만들 수 있다.
  • 유니온 타입은 강력하지만, 변경 시에는 주의해야 한다. 유니온에 한 타입을 추가하려면, 우선, 모든 구독자에 새로운 스키마 업데이트가 필요하고, 변경 내용을 알게 된다. 모든 구독자(readers)가 업데이트되면, 작성자(writers)는 생성하는 레코드에 새로운 타입을 추가할 수 있다.
  • 원하는 대로, 레코드에서 필드를 재정렬할 수 있다. 비록 필드는 정의된 순서로 인코딩되지만, 파서는 구독자와 작성자 스키마에서 이름으로 필드를 찾을 수 있고, 이것이 에이브로에서 태그 번호가 필요없는 이유이다.
  • 이름으로 필드를 찾을 수 있기에, 필드의 이름을 변경하는 것이 까다롭다. 새로운 필드 이름을 사용하기 위해서, 기존 이름의 별칭(구독자의 스키마에서 별칭을 이름 매칭에 사용한 이래)으로 유지하는 동안, 데이터의 모든 구독자는 먼저 업데이트가 필요하다. 다음에, 새로운 필드를 사용하기 위해서 작성자의 스키마를 업데이트할 수 있다.
  • 또, 기본 값(예로 null, 필드의 타입이 union이고 기본값이 null이면)을 정해서, 레코드에 필드를 추가할 수 있다. 구독자가 새로운 스키마로 기존 스키마(필드가 부족한)로 작성된 레코드를 파싱할 때, 대신 기본값으로 채울 수 있기에, 기본값이 필요하다.
  • 반대로, 레코드에서 기존에 기본값을 가지고 있는 필드를 삭제할 수 있다(이것이, 가능한 모든 필드에 기본값을 제공하는 것이 유익하다는 이유다). 구독자가 기존 스키마로 새로운 스키마로 작성된 레코드를 파싱할 때, 기본값으로 돌릴 수 있다.

이것은 작성된 특정 레코드의 정확한 스키마를 알아야 하는 문제를 남긴다. 그래서 가장 좋은 해결책은 데이터가 사용되고 있는 상황(context)에 따르는 것이다.

  • 일반적으로, 하둡(Hadoop)에서는 모두 같은 스키마로 인코딩된, 수백만의 레코드를 유지하는 파일이 많이 있다. 객체 컨테이너 파일(Object container files)이 이 파일들을 처리한다: 단지 파일의 시작 부분에서 스키마를 가지고 있고, 나머지 부분에 스키마로 디코딩할 수 있다.
  • RPC(Remote Procedure Call)에서, 요청과 응답에서 매번 스키마를 보내는 것은 너무 오버헤드가 클 것이다. 하지만 RPC 프레임웍이 오래 유지하는(long-lived) 연결을 사용한다면, 연결 시점에서 한번 스키마를 협상(주고받으면서 확인)할 수 있고, 많은 요청의 오버헤드 비용을 상쇄할 수 있다.
  • 데이터베이스에 하나씩 레코드를 저장한다면, 다른 시간대에 작성된 여러 스키마 버전으로 저장할 수 있고, 그래서 스키마 버전의 각 레코드에 주석이 필요하다. 스키마 자체를 저장하는 것이 너무 오버 헤드가 큰 경우, 스키마 해시나 차례대로 스키마 버전 번호를 사용할 수 있다. 이때는, 특정 버전 번호에 대해서, 정확한 스키마 버전을 찾을 수 있는 스키마 레지스트리(schema registry)가 필요하다.

이것을 고려하는 한 방법으로: 프로토콜 버퍼에서는, 레코드의 모든 필드를 태그하고, 에이브로에서는, 전체 레코드, 파일 또는 네트워크 연결을 스키마 버전으로 태그한다.

언뜻 보기에는, 스키마 배포에 부가적인 노력이 필요하기에, 에이브로 방식이 더 복잡하게 보일 수 있다. 하지만, 에이브로 방식이 가지고 있는 몇 가지 의미있는 장점에 대해서 생각해 보자:

  • 객체 컨테이너 파일의 스스로-기술하는(self-describing) 것은 매우 좋다: 파일에 내장된 작성자 스키마는 모든 필드의 이름과 타입을 포함하고 있고, 심지어 문서 문자열(스키마의 작성자(author)가 귀찮아하는 일부 일들)도 포함하고 있다. 이것이 피그(Pig)와 같은 대화형(interactive) 툴에 직접 해당 파일을 읽거나, 어떤 구성 없이도 바로 작업(Works™)할 수 있게 한다.
  • 에이브로 스키마는 JSON으로, 자신의 메타데이터를 파일에 추가(예로 어플리케이션 수준에서 필드에 대한 의미를 기술하는)할 수 있다.
  • 스키마 레지스트리는 아마도 문서(documentation)를 제공하고, 데이터를 찾거나 재사용하는 것을 도와줄 때에 유익할 것이다. 그리고 스키마 없이는 에이브로 데이터를 파싱할 수 없기에, 스키마 레지스트리는 최신으로 보장되어 있다. 물론, 프로토콜 버퍼 스키마 레지스트리도 역시 설정할 수 있지만, 필요한 기능이 없으므로, 단지 최선을 다했다는 근거만 될 것이다.

스리프트
스리프트는 데이터 직렬화 라이브러리 뿐 아니라, 전체가 RPC 프레임워크라서, 에이브로나 프로토콜 버퍼보다 큰 프로젝트이다. 스리프트는 또 다른 방식을 가지고 있다: 에이브로나 프로토콜 버퍼는 단일 바이너리 인코딩을 표준으로 하지만, 스리프트는 다양한 직렬화 포맷(“프로토콜”이라고 하는)을 포함(embraces)한다.

사실, 스리프트는 두 가지(12) JSON 인코딩 방식이 있으며, 최소한 3가지의 바이너리 인코딩 방식을 가지고 있다(하지만, 바이너리 인코딩의 하나인 DenseProtocol은 오직 C++ 구현만 지원하고 있다: 언어 중립적인(cross-language) 직렬화에 관심이 있기에, 다른 두 가지에 중점을 둘 것이다).

모든 인코딩은 스리프트 IDL에서 정의된 같은 스키마를 공유한다:

01
02
03
04
05
struct Person {
  1: string       userName,
  2: optional i64 favouriteNumber,
  3: list<string> interests
}

바이너리 프로토콜(BinaryProtocol) 인코딩은 매우 간단하지만, 상당히 비효율적(위의 예제 레코드를 인코딩하기 위해서 59바이트를 사용)이다:

컴팩트 프로토콜(CompactProtocol) 인코딩은 의미상 같지만, 크기를 34바이트로 줄이기 위해서 가변-길이 정수와 비트 패킹을 사용한다:

보다시피, 스리프트의 스키마 변화에 대한 대처 방식은 프로토콜 버퍼와 같다: 각 필드는 IDL에 수동으로 태그를 할당하고, 태그와 필드 타입은 인코딩된 바이너리에 저장하고, 이것으로 파서가 알지 못하는 필드는 생략할 수 있도록 한다. 스리프트는 프로토콜 버퍼의 반복 필드 방식보다 명시적인 리스트 타입을 정의하지만, 이 두 개는 매우 유사하다.

사상적인 관점에서, 위 라이브러리들은 매우 다른 관점을 가지고 있다. 스리프트는 전체가 통합된 RPC 프레임웍이고, 많은 선택(다양한 언어를 지원)을 주는 “한 상점에서 여러 가지 상품을 파는(one-stop shop)” 형태를 선호한다. 반면에, 프로토콜 버퍼와 에이브로는 “한 가지 일을 더 잘하는” 형태를 더 선호하는 것으로 보인다.

원 글에 있는 답글 중에 하나로 “에이브로는 스리프트보다는 작지만 RPC 프레임웍이고, IPC도 지원한다”고 한다.

반응형
Posted by blueasa
, |


Link : https://aws.amazon.com/ko/documentation/sdk-for-unity/

반응형
Posted by blueasa
, |

Amazon Web Services 한국 블로그

AWS Unity SDK 로 간단한 게임 구현하기

on  | in AWS LambdaAWS SDK한국 기술 문서한국 소식 | 

AWS는 다양한 프로그래밍 언어를 위한 소프트웨어 개발 도구(SDK)를 제공하고 있습니다. Python, Java, PHP 등은 물론이고, 최근에는 C/C++를 지원하는 SDK도 발표를 하였습니다. 또한, 모바일 앱을 개발을 지원하는 AWS Mobile SDK가 있습니다. AWS Mobile SDK는 iOS, Android, Fire OS 그리고 오늘 설명 드릴 Unity를 지원합니다.

Unity는 잘 알려진 게임 개발 통합 플랫폼입니다. 주로 3D게임을 편리하게 개발하기 위한 기능과 툴을 제공하고 있으며, Unity를 통해 개발된 많은 게임이 존재합니다. Unity는 게임 디자인부터 사운드와 물리 엔진 등의 여러가지 기능과 네트워크 기능도 제공하고 있습니다. AWS Mobile SDK는 기본적으로 Mobile apps에서 AWS 를 쉽게 접근할 수 있도록 함수를 제공하는 것이 기본적인 목적입니다.

따라서, AWS Mobile SDK for Unity는 Unity를 통해서 모바일 게임 등을 개발할 때 직접 Unity 코드에서 SDK를 이용하여 AWS 에서 제공하는 서비스들을 편리하게 사용할 수 있습니다.

아래 내용은 SurvivalShooter라는 공개 데모 게임에 AWS Unity SDK를 설치하고 개발하여 실제 게임에서 AWS Cognito를 통해 인증 및 데이타 싱크, S3에서 설정 파일 다운로드, Facebook과 인증 연동, 게임 정보와 로그를 DynamoDB에 기록, Mobile analytics로 게임 로그 전송을 구현한 예제입니다. 전체 코드를 참조하여 주시고, 설명은 각각의 주요 내용에 대한 핵심 부분을 간략히 설명하겠습니다.

☞샘플 코드: https://s3-ap-northeast-1.amazonaws.com/www.cloudinternal.com/SurvivalShooterAWS.zip

참고: 본 가이드는 AWS Mobile SDK for Unity Preview 버전을 기반으로 작성되어 최신 정식 SDK 버전과 약간의 차이가 있을 수 있습니다.

1. AWS Mobile SDK for Unity 소개

AWS Unity SDK는 현재(21-Oct-2015) 아래 AWS 서비스를 지원하고 있습니다. 아래 AWS 서비스를 사용하기 위한 .NET Classes를 제공하고 있어, Unity로 게임을 개발하면서 아래 서비스를 사용하기 위해 클래스를 호출하여 개발을 하게 됩니다. 최근에는 AWS Lambda를 추가로 지원하면서 Unity로 개발된 게임에서 Lambda 함수를 호출하여 (별도 백엔드 서버를 통한 처리가 아니라) 이벤트 기반 개발 로직 처리 및 구현이 가능합니다. SDK 내용을 보면 아래의 각 서비스의 플러그인 패키지로 구성되어 있습니다.

2. Unity 및 AWS Unity SDK 설치

AWS Unity SDK는 Unity version 4.x 와 5.x 의 버전과의 호환성을 가지고 있으며, 4.0~4.2버젼의 경우는 설치 시에 몇 가지 작업이 필요합니다. AWS Unity SDK의 소개와 설치에 대한 부분은 아래 링크에서 상세히 가이드하고 있습니다. 아래 링크를 참조하시어 개발 환경 구성을 구성하십시오. 순서를 요약하면 아래와 같습니다.

  1. Unity 다운로드 및 설치
  2. AWS Unity SDK 다운로드 설치
  3. AWS Unity SDK 설정 (생략을 하고 코드에서 직접 지정을 할 수도 있습니다.)
  4. Amazon Cognito를 통해 AWS Credentials 획득
  5. 주요 개발 문서:

3. Amazon Cognito 인증하기

AWS Unity SDK에서 AWS 리소스에 접근하기 위해서는 인증이 필요하게 됩니다. 인증을 위한 정보가 바로 AWS Credential입니다. 보통 access key, secret key로 구성하나, 모바일 게임을 개발하면서 지정된 키를 직접 코드에 넣는 것은 좋은 방법이 아닙니다. 모바일 인증을 손쉽게 하기 위한 빌딩 블럭인 Amazon Cognito를 통해 쉽게 구성할 수 있습니다.

Amazon Cognito는 Identity pool을 구성하고 해당 풀에 사용자 정보를 저장할 수 있습니다. 그리고 해당 Identity pool에 IAM 역할(role)을 정의하여 게임 사용자가 어떤 AWS 서비스를 이용할 수 있는지 정할 수 있습니다. 예를 들어 IAM role에서 DynamoDB에 PutItem API를 허락하도록 추가한다면 사용자는 AWS Cognito를 통해 전달 받은 임시 토큰을 통해 DynamoDB에 직접 데이터를 입력할 수 있습니다.

그 과정을 살펴 보도록 하겠습니다. 먼저 아래 아래 코드와 같이 AWSManager.cs 파일에 Credential provider를 생성합니다.

C#

public string IDENTITY_POOL_ID = "";
public string ANALYTICS_APP_ID = "";
public RegionEndpoint ENDPOINT = RegionEndpoint.USEast1;

private CognitoAWSCredentials credentials;
private CognitoSyncManager syncManager;

// Create a Credentials provider that uses Cognito Identity
credentials = new CognitoAWSCredentials(IDENTITY_POOL_ID, ENDPOINT);

IDENTITY_POOL_ID를 입력 받아 코드에서 해당 Identity pool ID를 읽어 인증을 진행하게 됩니다. Unity개발 도구의 Inspector에서 생성한 ID를 입력하도록 합니다. AWS 콘솔에서는 아래와 같이 Amazon Cognito 메뉴로 이동하여, Identity pool을 생성하여 인증 사용자와 비인증 사용자가 이용할 IAM 역할(Role)을 선택합니다. 위에서 설명 드린 것처럼 IAM Role에 AWS 서비스에 접근할 수 있는 권한이 주어져야 개발된 게임에서 해당 서비스에 접근을 할 수 있습니다.

4. Amazon Cognito 데이터 싱크

Amazon Cognito는 사용자 별로 데이터를 각각 저장하고 동기화하는 기능을 제공합니다. 멀티 플랫폼에서 사용자와 애플리케이션 정보를 유지하여 지속적으로 서비스에 활용하도록 하는 기능입니다. 데모 게임에서는 플레이어의 위치를 Cognito Dataset에 저장하고 다음 플레이에 해당 위치에서 시작하도록 예를 구성하였습니다.

AWSManager 클래스에서 CognitoSynchManager를 생성

C#


// Cognito Sync Manager
syncManager = new DefaultCognitoSyncManager(
credentials, new AmazonCognitoSyncConfig { RegionEndpoint = ENDPOINT });
//UserDataManager에서 플레이어 위치 정보를 저장하고 읽어 올 수 있도록 구현합니다.
public Dataset GetUserProfileDataset() {
	return userProfileDataset;
}

public void SetPlayerPosition(Vector3 pos) {
	userProfileDataset.Put("position", NDCUtil.stringFromVector3(pos));
}

public Vector3 GetUserPosition() {
	Vector3 pos = NDCUtil.vector3FromString(userProfileDataset.Get("position"));
	if (pos == null) {
		pos = new Vector3(0, 0, 0);
		userProfileDataset.Put("position", NDCUtil.stringFromVector3(pos));
	}
return pos;
}

public void SyncUserProfile() {
	userProfileDataset.Synchronize();
}

Amazon S3 파일 가져오기

Amazon S3 스토리지 서비스를 게임 자원의 DLC(Downloadable Content)에 활용하면 편리하게 파일 형태의 데이터를 저장하고 읽어 올 수 있습니다. 정적 콘텐츠(Static Contents)는 물론 게임 내에서 활용할 설정이나 환경 설정(Configuration) 정보를 저장하여 게임 시작 시 다운로드하여 게임에 쉽게 활용할 수 있습니다.

이 예제 게임에서는 S3 URL을 입력하여 간단한 JSON 형태의 입력 파일을 게임 시작 시 다운로드 합니다. DLCManager 클래스에서 다운로드 코드를 구현합니다. S3를 보다 잘 사용하기 위해서는 AmazonS3Client를 이용하면 편리합니다.

C#

public string DLC_URL = "";
public void CheckDLC() {
	WWW www = new WWW(DLC_URL);
	print (GameManager.Instance);
	StartCoroutine(WaitForDLCRequest(www));
}

IEnumerator WaitForDLCRequest(WWW www) {
	yield return www;

	// check for errors
	if (www.error == null) {
		GameManager.Instance.UpdateStatusMessage("DLC updating...");
		Debug.Log("WWW Ok!: " + www.text);
		ParseAndUpdateDLC(www.text);
		GameManager.Instance.UpdateStatusMessage("Facebook initializing...");
	//  FB.Init(FacebookInitCallback);

		GameManager.Instance.OnDLCUpdateEnded();
	} else {
		Debug.Log("WWW Error: "+ www.error);
		GameManager.Instance.UpdateStatusMessage("Failed to update content: " + www.error, false);
		}
}

AmazonS3Client를 활용할 경우 아래의 예제 코드를 간단히 변경하여 적용이 가능합니다.

C#

private void GetObject()	{
	ResultText.text = string.Format("fetching {0} from bucket {1}",
	SampleFileName, S3BucketName);
	Client.GetObjectAsync(S3BucketName, SampleFileName, (responseObj) =< {
		string data = null;
		var response = responseObj.Response;
	        if (response.ResponseStream != null) {
		      using (StreamReader reader = new StreamReader(response.ResponseStream)) {
		      data = reader.ReadToEnd();
		     }
	        ResultText.text += "\n";
	        ResultText.text += data;
	       }
        });
}

5. Facebook과 인증 연동하기

Amazon Cognito Identity 는 Facebook, Google, Amazon, Twitter/Digits 및 OpenID를 이용한 연동 인증을 지원합니다. 이 게임 예제에서는 Facebook을 인증 제공자로 선택하여 인증할 수 있도록 구현하였습니다.

앞에 설명한 것처럼 Cognito는 비인증 사용자(Unauthenticated user)와 인증 사용자(Authenticated user)를 구분하여 지원할 수 있습니다. Facebook developers 페이지에서 앱을 등록하여 ID를 발급받아 Amazon Cognito Identity pool에 등록하는 과정이 필요합니다.

아래와 같이 Facebook developer 페이지에서 앱을 등록하여 ID를 발급받습니다.

Amazon Congito 에서 생성한 Identity pool의 설정에 아래와 같이 ID를 등록합니다.

보다 상세한 내용은 아래 링크를 참조해 주십시오.

http://docs.aws.amazon.com/cognito/devguide/identity/external-providers/facebook/

Unity에 Facebook 연동으로 위해 Facebook SDK를 설치합니다. 아래 링크에서 다운로드가 가능합니다.

https://developers.facebook.com/docs/android

Facebook 연동 인증은 FacebookManager.cs로 구현되어 있습니다. GUI로 화면에서 로긴 정보를 입력할 수 있도록 구현되어 있습니다.

위에서 Find Access Token을 누르면 Facebook 페이지에서 사용자 토큰(User token)을 발급받아 입력할 수 있습니다. 키를 입력하고 Login을 누르면 Facebook 과 연동하여 Authenticated user로 인증을 진행 할 수 있습니다.

6. Amazon Mobile Analytics 사용하기

Amazon Mobile Analytics 서비스는 모바일 게임 또는 서비스에서 나오는 사용자 및 매출 통계, 캠페인을 통한 결과 등 비지니스를 위한 다양한 정보를 그래프와 함께 제공해 주는 기능입니다. 또한 맞춤형 이벤트(Custom Events)를 만들어주면 원하는 통계를 받아 볼 수 있습니다. 게임이라면 게임 내에 일어나는 다양한 이벤트를 Amazon Mobile Analytics로 간단히 추가해서 반영할 수 있습니다.

이 게임 예제에서는 게임 중에 Enemy와 Damage에 대한 정보와, 세션 시간에 대한 정보를 예제로 Amazon Mobile Analytics Custom events로 보내는 내용이 구현되어 있습니다. 참조하여 다양한 정보를 간단한 코드 추가로 분석해 볼 수 있는 기능입니다.

Amazon Mobile Analytics를 이용하기 위해서는 App을 등록하는 과정이 필요합니다 등록하여 ID를 발급 받아 해당 아디이로 생성한 App에 데이터를 넣을 수 있게 됩니다. 아래 그림은 콘솔에서 Mobile analytics app을 생성한 내용입니다.

위에 App ID를 Unity inspector에서 Cognito ID처럼 동일하게 값을 입력하여 코드에서 읽어 사용하게 됩니다.

AWSManage 클래스에 구현되어 있는 코드는 아래와 같습니다. AnalyticsManager를 생성하고 원하는 통계치 메트릭을 추가합니다.

C#

// Amazon Mobile Analytics Manager
analyticsManager = AmazonMobileAnalyticsManager.GetOrCreateInstance(
	credentials, ENDPOINT, ANALYTICS_APP_ID);
//-------
public void RecordEnemyHit(string weapon, int damage, string enemyName, int score) {
	AmazonMobileAnalyticsEvent customEvent = new AmazonMobileAnalyticsEvent("ShooterEnemyHit");
	customEvent.AddAttribute("Enemy", enemyName);
	customEvent.AddMetric("Damage", damage);
	analyticsManager.RecordEvent(customEvent);
}

public void RecordSessionTime(int time) {
	AmazonMobileAnalyticsEvent customEvent = new AmazonMobileAnalyticsEvent("SessionTime");
	customEvent.AddMetric("SessionTime", time);
	analyticsManager.RecordEvent(customEvent);
}

DynamoDB에 데이터 적재하기

AWS Unity SDK는 직접 DynamoDB에 데이터를 넣고 쓸 수 있도록 지원하고 있습니다. 중간에 다른 Back-end server를 구현하지 않고, 직접 NoSQL 데이터베이스인 DynamoDB를 읽고 쓸 수 있는 방법은 비용을 줄이고 빠르게 개발할 수 있는 장점이 있습니다. 이 게임 예제에서는 UserInfo와 ShotInfo라는 두 개의 테이블을 만들어 사용자 정보와 사용자가 게임을 하면서 발생하는 적과 데미지 무기 종류에 대한 정보를 지속적으로 DynamoDB에 넣을 수 있도록 구현하였습니다.

아래는 코드 예제로 Cognito 인증 정보를 통해 DynamoDB client객체를 생성하고 데이터를 넣는 예제입니다.

C#

// Dynamo DB Client
dynamoClient = new AmazonDynamoDBClient(new CognitoAWSCredentials(IDENTITY_POOL_ID, ENDPOINT), ENDPOINT);
dynamoContext = new DynamoDBContext(dynamoClient);
// -----

public void PutUserShotInfo(string weapon, int damage, string enemyName, int score) {
	string userId = GameManager.Instance.userDataManager.userId;
	UserShotInfo userShotInfo = new UserShotInfo{
	Id = userId,
	Timestamp = NDCUtil.CurrentTimestamp(),
	Weapon = weapon,
	Damage = damage,
	Score = score
};
dynamoContext.SaveAsync<UserShotInfo>(userShotInfo,
(AmazonDynamoResult<VoidResponse> r) =< {
		if (r.Exception != null) {
		Debug.LogError("Save error");
		Debug.LogException(r.Exception);
		return;
		}
	}, null);
}

AWS Mobile SDK를 통해 서버를 통하지 않고 직접 AWS의 다양한 서비스를 활용하는 방법은 최근 이야기되는 Serverless architecture를 이용하여 낮은 비용과 빠른 개발 시간을 통해 다양한 게임을 개발할 수 있는 새로운 방법을 제공하여 줍니다.

더불어 Unity라는 게임 개발 플랫폼에서 게임 개발과 동시에 AWS의 서비스를 직접 활용할 수 있도록 하는 AWS Mobile SDK for Unity는 예제와 같이 단순한 코드로 콘텐츠, NoSQL DB, 분석 등의 다양한 기능을 직접 구현할 수 있도록 게임 개발자 분들에게 편리한 기능을 제공합니다. Unity로 게임을 개발하면서 Back-end service를 AWS로 구성하여 다양한 게임을 개발하는데 도움이 되었으면 합니다.

본 글은 아마존웹서비스 코리아의 솔루션즈 아키텍트가 국내 고객을 위해 전해 드리는 AWS 활용 기술 팁을 보내드리는 코너로서, 이번 글은 김일호 솔루션즈 아키텍트께서 작성해주셨습니다.


출처 : https://aws.amazon.com/ko/blogs/korea/aws-unity-sdk-game/

반응형
Posted by blueasa
, |
반응형
Posted by blueasa
, |

아마존 웹 서비스는 시간당 사용료를 내고 서버를 사용하는 IaaS(Infrastructure as a service)입니다.

 

일반 호스팅 서비스와는 달리 매우 다양한 기능을 가지고 있습니다.

 

- EC2: 가상 서버

- CloudWatch: 리소스 모니터링 서비스

- S3: 스토리지 서비스

- Glacier: 매우 저렴한 데이터 저장 서비스

- CloudFront: CDN 서비스

- RDS: MySQL 등의 관계형 DB 서비스

- DynamoDB: NoSQL 서비스

- ElastiCache: 인 메모리 캐시 서비스

- Route 53: DNS 서비스

- ELB: 로드밸런서(부하 분산 서비스)

- Auto Scaling: 트래픽에 따른 횡적 확장(EC2 추가 및 삭제)

- CloudFormation: 서버 구성 자동화

- Elastic Beanstalk: PaaS

- OpsWorks: Chef를 기반으로 하는 PaaS

- CloudSearch: 검색 서비스

- SNS: 푸시 알림 서비스

- SES: 이메일 전송 서비스

- SQS: 메시지 큐 서비스

- Elastic Transcoder: 동영상 인코딩 서비스

 

처음 가입하면 1년동안 AWS를 무료로 사용할 수 있는 프리 티어가 제공됩니다.

 

독자적으로 서버를 구축하여 블로그나 사이트를 운영할 때 유용합니다.

 

저는 제 블로그를 PaaS 서비스인 Heroku로 구축을 하고, Heroku의 느린 속도를 개선하기 위해 CDN 서비스인 CloudFront를 연동해서 운영하고 있습니다. 운영비는 도메인을 연결한 Route 53만 한달에 $0.5가 나오고 있습니다. Heroku는 Dyno가 1개라 무료고, CloudFront도 프리 티어라 무료로 사용하고 있습니다.

 

제가 이번에 AWS에 관한 책을 썼는데요. 원고도 모두 인터넷에 공개하게 되었습니다. 많은 분들이 보고 활용해주셨으면 합니다.

 

http://www.pyrasis.com/private/2014/09/30/publish-the-art-of-amazon-web-services-book




출처 : http://www.clien.net/cs2/bbs/board.php?bo_table=lecture&wr_id=243080

반응형
Posted by blueasa
, |

Thrift는 Facebook의 직렬화 방법이다. 2010년 3월 현재 apache의 인큐베이팅 프로젝트로 등록되어 있다. 전 구굴의 개발자가 만들었다고 하고 Protocol Buffers와 무척 유사하다. 하며, 다음과 같은 차이점이 있다.

- java API가 좀더 깔끔하다.
    - 별도의 setter, getter없이 필드에 직접 접근
    - 별도의 read only의 Message와 read & write의 Builder의 구분이 없다.
- c++의 경우 생성된 코드는 정의한 필드의 소대문자가 전부 소문자로 변하는데 반해 Thrift는 소대문자가 보존된다.
    - Protocol Buffers : message->set_myname(2);
    - Thrift : message->myName = 2;
- Protocol Buffers는 필드의 List만 지원하는데 반해 Thrift는 Set, Map, List를 지원한다.
- RPC 인터페이스 정의에 의한 구현코드가 자동생성된다. 서버, 클라이언트 코드의 작성이 쉽다.


이외의 것은 Protocol Buffers와 거의 똑같다.

장단점을 비교하면 다음과 같다.

Protocol Buffers
    - 장점
        - Thrfit에 비해 빠르다.(3배정도)
    - 단점
        - java의 경우 Message, Builder로 분리되어 있고, Builder를 사용한 코드가 다소 지저분하다.
        - List만 처리 가능(과연 단점일까?)

Thrift        
    - 장점
        - List, Set, Map 지원
    - 단점
        - c++의 경우 메시지가 다른 메시지 필드의 타입일 경우 손수 생성, 파괴하여야 한다.
        - 문서가 거의 없다. 샘플도 거의 없다.
        - 윈도우에서는 컴파일러를 컴파일하기 까다롭다.
        - 자동 생성된 c++코드는 windows에서는 사용하지 못한다.


기타 특징은 다음과 같다.

    - c++, c#, cocoa, java, perl, php, python, ruby를 지원한다.

    - Apache License 2.0

    - 상수와 enum을 지원

    - 하위 호환

    - 서비스의 상속 가능

    - 비동기시 서비스 지원

    - 예외 지원

성능 테스트

두가지 사이즈의 데이타에 대하여 테스트 하였다.
인코딩된 사이즈의 크기는 Protocol Buffers가 34, 1234 byte였으며 Thrift는 174, 1504 byte 였다.
c++의 경우 처리 건수는 Protocol Buffers가 초당 130만회, 11만회였으며, Thrift는 초당 36만회, 8만회였다.
java의 경우 처리 건수는 Protocol Buffers가 초당 87만회, 6만회 였으며, Thrift는 초당 14만회, 4만회였다.


코드 샘플

- c structure

 typedef struct Css {
  int srcCSID;
  int newCSID;
 } Css;
 
 typedef struct Leg{
  int srcCSID;
  int newCSID;
  int srcLegID;
  int newLegID;
 } Leg;
 
 typedef struct StringValue {
  int length;
  char* str;
 } StringValue;

 typedef struct Messsage {
  int result;
  int vcID;
  int srcCSAID;
  int targetCSAID;
  Css[] csses;
  Leg[] legs;
  StringValue serviceID;
 } Message;

 

- Protocol Buffers definition

 message Css {
  optional uint32 srcCSID = 1;
  optional uint32 newCSID = 2;
 }

 message Leg {
  optional uint32 srcCSID = 1;
  optional uint32 newCSID = 2;
  optional uint32 srcLegID = 3;
  optional uint32 newLegID = 4;
 }
 
 message StringValue {
  optional uint32 length = 1;
  optional string str = 2;
 }

 message Message {
  optional uint32 result = 1;
  optional uint32 vcID = 2;
  optional uint32 srcCSAID = 3;
  optional uint32 targetCSAID = 4;
  repeated Css css = 5;
  repeated Leg leg = 6;
  optional StringValue serviceID = 7;
 }

 

- Thrift definition

 struct Css {
  1: i64 srcCSID,
  2: i64 newCSID
 }

 struct Leg {
  1: i64 srcCSID,
  2: i64 newCSID
  3: i64 srcLegID,
  4: i64 newLegID
 }

 struct StringValue {
  1: i64 length,
  2: string str
 }
 
 struct Message {
  1: i64 result,
  2: i64 vcID,
  3: i64 srcCSAID,
  4: i64 targetCSAID,
  5: list<Css> csses,
  6: list<Leg> legs
  7: StringValue serviceID
 } 



 - c++ - Protocol Buffers

 ShortTasMessage::Message* message = new ShortTasMessage::Message();

 message->set_result(100);
 message->set_vcid(9);
 message->set_srccsaid(1);
 message->set_targetcsaid(2);

 ShortTasMessage::Css* css = message->add_css();
 css->set_srccsid(1);
 css->set_newcsid(2);

 ShortTasMessage::Leg* leg0 = message->add_leg();
 leg0->set_srccsid(1);
 leg0->set_newcsid(2);
 leg0->set_srclegid(1);
 leg0->set_newlegid(3);

 ShortTasMessage::Leg* leg1 = message->add_leg();
 leg1->set_srccsid(1);
 leg1->set_newcsid(2);
 leg1->set_srclegid(2);
 leg1->set_newlegid(4);
 
 LongTasMessage::StringValue* serviceID = message->mutable_serviceid();
 serviceID->set_length(3); 
 serviceID->set_str("CHD");


  
c++ - Thrift

 ShortTasMessage::Message* message = new ShortTasMessage::Message();

 message->result = 100;
 message->vcID = 9;
 message->srcCSAID = 1;
 message->targetCSAID = 2;

 ShortTasMessage::Css* css = new ShortTasMessage::Css();
 css->srcCSID = 1;
 css->newCSID = 2;
 message->csses.push_back(*css);  // csses is List
 delete css; css = NULL;

 ShortTasMessage::Leg* leg0  = new ShortTasMessage::Leg();
 leg0->srcCSID = 1;
 leg0->newCSID = 2;
 leg0->srcLegID = 1;
 leg0->newLegID = 3;
 message->legs.push_back(*leg0);
 delete leg0; leg0 = NULL;

 ShortTasMessage::Leg* leg1  = new ShortTasMessage::Leg();
 leg1->srcCSID = 1;
 leg1->newCSID = 2;
 leg1->srcLegID = 2;
 leg1->newLegID = 4;
 message->legs.push_back(*leg1);
 delete leg1; leg1 = NULL;
 
 message->serviceID.length = 3;
 message->serviceID.str = "CHD"; 



java - protocol buffers

 ShortTasMessage.Message.Builder message = ShortTasMessage.Message.newBuilder();

 message.setResult(100);
 message.setVcID(9);
 message.setSrcCSAID(1);
 message.setTargetCSAID(2);
 
 ShortTasMessage.Css.Builder css = ShortTasMessage.Css.newBuilder();
 css.setSrcCSID(1);
 css.setNewCSID(2);
 message.addCss(css);

 ShortTasMessage.Leg.Builder leg0 = ShortTasMessage.Leg.newBuilder();
 leg0.setSrcCSID(1);
 leg0.setNewCSID(2);
 leg0.setSrcLegID(1);
 leg0.setNewLegID(3);
 message.addLeg(leg0);

 ShortTasMessage.Leg.Builder leg1 = ShortTasMessage.Leg.newBuilder();
 leg1.setSrcCSID(1);
 leg1.setNewCSID(2);
 leg1.setSrcLegID(2);
 leg1.setNewLegID(4);
 message.addLeg(leg1);
 
 LongTasMessage.StringValue.Builder serviceID = LongTasMessage.StringValue.newBuilder();
 serviceID.setLength(3);
 serviceID.setStr("CHD");
 message.setServiceID(serviceID);


  

java - Thrift  

 ShortTasMessage.Message message = new ShortTasMessage.Message();

 message.setResult(100);
 message.setVcID(9);
 message.setSrcCSAID(1);
 message.setTargetCSAID(2);

 ShortTasMessage.Css css = new ShortTasMessage.Css();
 css.setSrcCSID(1);
 css.setNewCSID(2);
 message.addToCsses(css);

 ShortTasMessage.Leg leg0 = new ShortTasMessage.Leg();
 leg0.setSrcCSID(1);
 leg0.setNewCSID(2);
 leg0.setSrcLegID(1);
 leg0.setNewLegID(3);
 message.addToLegs(leg0);

 ShortTasMessage.Leg leg1 = new ShortTasMessage.Leg();
 leg1.setSrcCSID(1);
 leg1.setNewCSID(2);
 leg1.setSrcLegID(2);
 leg1.setNewLegID(4);
 message.addToLegs(leg1);
 
 LongTasMessage.StringValue serviceID = new LongTasMessage.StringValue();
 serviceID.setLength(3);
 serviceID.setStr("CHD");
 message.setServiceID(serviceID); 




Reference

demo client code
     http://www.google.co.kr/codesearch/p?hl=ko#W9kbJs3X16M/trunk/src/examples/thrift/DemoClient.cpp&q=TBinaryProtocol&d=4 
benchmark of thrift
    http://www.google.co.kr/codesearch/p?hl=ko#NPWGicy0Ryg/trunk/test/Benchmark.cpp&q=TBinaryProtocol%20lang:c%2B%2B&sa=N&cd=9&ct=rc
Simple tutorial
    http://skorage.org/2009/03/08/simple-thrift-tutorial/




Link : http://egloos.zum.com/aploit/v/5233573



반응형
Posted by blueasa
, |



링크 : http://knight76.tistory.com/entry/Thrift-vs-Protocol-Buffers-%EB%B9%84%EA%B5%90%EB%AC%B8-%EC%9A%94%EC%A0%90-%EC%A0%95%EB%A6%AC-%EB%B0%9C%EB%B2%88%EC%97%AD



반응형
Posted by blueasa
, |


Link : https://nodejstools.codeplex.com/

반응형

'Server > Node.js' 카테고리의 다른 글

Unity3d + Node.js 연동 테스트  (0) 2014.06.28
Node.js의 문서의 한글 번역본  (0) 2014.06.21
Node.js  (0) 2014.06.21
Posted by blueasa
, |


Link : http://smilejsu.tistory.com/673

반응형

'Server > Node.js' 카테고리의 다른 글

[Plugin] Node.js Tools for Visual Studio  (0) 2014.07.14
Node.js의 문서의 한글 번역본  (0) 2014.06.21
Node.js  (0) 2014.06.21
Posted by blueasa
, |