37. 클라이언트 지원 Part 1: 플러그인

클라이언트 플러그인을 사용하면 네트워크 통신, 공지사항 확인, 리소스 다운로드 등의 기능을 편리하게 사용할 수 있습니다. 현재 Unity, Unreal Engine 4, Cocos2d-x에서 사용할 수 있는 클라이언트 플러그인을 지원하고 있습니다. 클라이언트 플러그인은 GitHub 에서 받으실 수 있습니다.

이 문서에서는 클라이언트 플러그인의 사용방법, 주의할 사항, 사용팁 등을 설명합니다. 아래의 설명은 Unity 플러그인을 기준으로 작성되었습니다.

37.1. 개요

37.1.1. Session이란?

서버와 클라이언트의 네트워크 연결은 Session으로 관리합니다. Session은 직접 Close하거나, 설정한 시간 이상 네트워크 통신이 이루어지지 않아 Timeout이 발생하지 않는한 유지됩니다. 즉, 네트워크 연결이 끊기더라도 서버에서는 Session을 유지하며, 재연결시 동일한 Id의 Session이 있다면 연결이 끊어지지 않은 것처럼 계속 메시지를 주고 받을 수 있습니다.

하나의 Session은 TCP, UDP, HTTP 하나씩 최대 3개의 Transport 연결을 가질 수 있습니다. 같은 프로토콜 연결을 여러 개 만들고 싶다면 Session도 여러 개 만들어야 합니다.

Tip

Session의 내부 구현 방식과 관련된 자세한 내용은 (고급) 아이펀 엔진의 네트워크 스택 에서 설명하고 있습니다.

37.1.1.1. Session Id

서버에 접속하면 제일 먼저 서버로부터 Session Id 를 발급 받게 되고 이 후 서버와 주고 받는 모든 메시지에는 이 Session Id 가 포함됩니다.

JSON을 사용하는 경우, Session Id는 36 bytes 문자열입니다. Protobuf를 사용하는 경우에는 16 bytes Array, 혹은 JSON과 동일한 36 bytes 문자열로 사용할 수 있습니다. 서버와 주고 받는 모든 메세지에 Session Id가 포함되므로, 16 bytes Array를 사용하면 네트워크 사용량을 줄일 수 있습니다.

Protobuf에서 Session Id를 Byte Array로 사용하려면, 서버 MANIFEST 파일의 send_session_id_as_string 값을 false 로 설정하면 됩니다. 이 옵션의 기본 값은 true 입니다. JSON을 사용하는 경우, 이 옵션은 아무 동작도 하지 않고, 계속 36 bytes 문자열을 사용합니다.

Tip

개발 편의를 위하여 플러그인의 초기 설정은 연결할 때마다 새로운 Session Id 를 요청하는 것입니다. 클라이언트에서도 동일한 Session Id 를 계속 사용하고 싶다면 Session Reliability 옵션을 사용해 주세요.

37.1.1.2. FunapiSession 클래스

플러그인을 더 쉽고 편하게 사용할 수 있도록, 기존 FunapiNetwork 클래스를 대체하는 FunapiSession 클래스가 추가되었습니다.

FunapiNetwork는 더 이상 업데이트되지 않습니다. (플러그인 버전 158 이후)

FunapiSession 클래스는 기존 FunapiNetwork 클래스의 기능 대부분을 가지고 있고, 인터페이스와 콜백 함수 등이 간결해졌습니다. 기존 FunapiNetwork를 사용하는 분들도 몇 가지 변경된 사항들만 숙지한다면 쉽게 FunapiSession으로 전환하실 수 있습니다.

기존의 FunapiNetwork에 대한 도움말은 이전 클라이언트 플러그인 설명 문서를 참고해주세요.

37.1.1.3. Session Reliability 옵션

Session reliability 는 FunapiSession 클래스를 생성할 때 전달하는 Boolean 타입의 파라미터입니다.

서버와 연결이 끊겨도 재연결시 동일한 세션으로 계속해서 통신할 수 있는 옵션입니다. 서버에 재접속 후 거쳐야 하는 인증 과정을 생략할 수 있고, 서버에서도 유저의 데이터 값을 그대로 유지할 수 있습니다. 모바일처럼 연결이 자주 끊길 수 있는 환경에서는 이 옵션을 사용하는 것을 추천합니다.

Tip

Session reliability 기능은 TCP 프로토콜에서만 사용 할 수 있습니다.

Session reliability는 서버와 클라이언트가 항상 동일한 설정을 사용해야 합니다. 서버 MANIFEST 파일의 use_session_reliability 옵션을 true로 설정했다면, 클라이언트에서도 FunapiSession 클래스를 생성할 때, session_reliability 파라미터에 true를 넘겨줘야 합니다. 이 값이 서로 다른 경우, 서버에 아래와 같은 로그가 출력됩니다.

// 서버는 Session reliability 옵션이 꺼져 있고 클라이언트는 켜져 있을 경우...
message with seq number. but seq number validation disabled: sid=..., transport_protocol=Tcp

// 서버는 Session reliability 옵션이 켜져 있고 클라이언트는 꺼져 있을 경우...
message without seq number: session_id=...

37.2. 서버에 연결하기

플러그인을 활용하여 구현하는 방법을 코드 예제와 함께 간략히 소개합니다. 전체 코드는 플러그인에 포함된 예제 프로젝트에서 확인하실 수 있습니다.

37.2.1. FunapiSession 객체 생성

서버와 통신하기 위해서 FunapiSession 클래스를 생성하고, 설정하는 방법입니다.

// FunapiSession 객체를 생성합니다.
// 전달하는 파라미터는 서버 주소와 Session reliability 옵션 사용 여부입니다.
FunapiSession session = FunapiSession.Create("127.0.0.1", true);

// 받은 메시지를 처리하기 위한 콜백을 등록합니다.
session.ReceivedMessageCallback += onReceivedMessage;

// 이벤트 콜백을 등록합니다.
session.SessionEventCallback += onSessionEvent;
session.TransportEventCallback += onTransportEvent;
session.TransportErrorCallback += onTransportError;

// Transport 옵션과 port를 지정한 후 연결을 시도합니다.
ushort port = getPort(protocol, encoding);
TransportOption option = makeOption(protocol);

session.Connect(protocol, encoding, port, option);

플러그인에서 사용하는 콜백은 유니티의 event 객체를 사용하고 있습니다. 이벤트로 콜백을 등록하는 방법은 예제와 같이 ‘+=’ 연산자를 사용합니다. 하나의 이벤트에 여러 개의 콜백을 등록할 수도 있습니다.

마지막 라인의 FunapiSession.Connect 함수는 지정한 파라미터를 활용하여 Transport를 생성하고 연결을 시도합니다.

이미 해당 protocol 의 Transport가 존재한다면 다시 연결하지 않고 해당 Trnasport를 재활용합니다. 이 경우 option 파라미터가 무시되므로, 특정 Transport로 재접속하는 경우, protocol 만 지정하는 FunapiSession.Connect 함수를 사용하시는 것이 좋습니다.

아래는 FunapiSession.Connect 함수에서 사용할 수 있는 protocolencoding 의 종류를 선언한 코드입니다.

// 프로토콜은 TCP, UDP, HTTP 3가지 타입을 지원합니다.
public enum TransportProtocol
{
  kDefault = 0,
  kTcp,
  kUdp,
  kHttp
};

// 메시지 인코딩 방식은 JSON과 Protobuf 2가지 방법이 있습니다.
public enum FunEncoding
{
  kNone,
  kJson,
  kProtobuf
}

37.2.2. 이벤트 콜백 함수

FunapiSession에는 Session의 상태 변화를 알려주는 콜백과 Transport의 상태 변화를 알려주는 콜백 함수가 있습니다. 또한 이와 별도로 Transport의 내부 에러에 대해서 알려주는 콜백 함수도 있습니다. Transport 내부에서 오류가 발생했을 경우, 에러 콜백과 함께 Transport의 이벤트 콜백이 함께 호출될 수도 있습니다.

37.2.2.1. Session Event

SessionEventCallback 에 콜백 함수를 등록하면 세션의 상태가 변경될 때마다 아래와 같은 세션 관련 이벤트 알림을 받을 수 있습니다.

public enum SessionEventType
{
  kOpened,           // 세션이 처음 연결되면 호출됩니다. 같은 세션으로 재연결시에는 호출되지 않습니다.
  kChanged,          // 세션 Id가 변경되면 호출됩니다. 정상적인 상황이라면 이 이벤트는 발생되지 않습니다.
  kStopped,          // 세션에 연결된 모든 Transport의 연결이 끊기면 호출됩니다.
  kClosed,           // 세션이 닫히면 호출됩니다. Transport의 연결이 끊겼다고 세션이 닫히지는 않습니다.
                    // 세션은 서버에서 명시적으로 Close가 호출되거나 세션 타임아웃이 되었을 때 종료됩니다.
  kRedirectStarted,  // Redirect 관련 이벤트는 아래 [서버간 이동] 항목을 참고해주세요.
  kRedirectSucceeded,
  kRedirectFailed
};

FunapiSession 에 있는 SessionEventCallback 은 다음과 같이 선언되어 있습니다.

void SessionEventHandler (SessionEventType type, string session_id);

37.2.2.2. Transport Event

Transport와 관련된 이벤트는 아래와 같습니다. kStarted 를 제외하고는 모두 연결에 실패하거나 연결이 끊겼을 때 발생하는 이벤트들입니다. kStopped 는 정상적으로 연결이 종료되었거나 오류로 인해 연결이 끊겼거나 Transport의 연결이 끊기면 항상 발생하는 이벤트입니다.

public enum TransportEventType
{
  kStarted,              // Transport가 시작되면 호출됩니다.
  kStopped,              // Transport가 종료되면 호출됩니다.
  kConnectionFailed,     // 연결에 실패하면 호출됩니다.
  kConnectionTimedOut,   // ConnectionTimeout 시간 초과로 연결에 실패하면 호출됩니다.
  kDisconnected          // 의도치 않게 연결이 끊겼을 때 호출됩니다.
};

kDisconnected 는 물리적인(소켓) 연결이 끊기거나 핑 타임아웃이 되었을 때 호출되는데 물리적인 연결이 끊긴 걸 감지하는 시점이 기기의 종류나 통신망의 상태에 따라 많이 다를 수 있습니다. 핑을 사용한다면 Disconnected 이벤트로 연결이 끊긴 것을 판단해도 좋으나 핑을 사용하지 않을 경우에는 단순히 Disconnected 이벤트만으로 연결이 끊긴 것을 판단하지 마시고 서버와의 주기적인 통신을 통해 확인하는 것을 권장합니다.

FunapiSession 에 있는 TransportEventCallback 은 다음과 같이 선언되어 있습니다.

void TransportEventHandler (TransportProtocol protocol, TransportEventType type);

37.2.2.3. Transport Error

Tranpsort의 에러 콜백의 경우에는 에러 타입과 함께 에러가 발생한 메시지의 타입도 전달됩니다.

Transport의 오류가 발생했을 때 연결을 시도하는 중이었다면 내부적으로 연결 시도가 종료되고 kConnectionFailed 이벤트가 호출됩니다. 서버와 연결이 된 상태에서 오류가 발생했을 때에는 에러만 전달하고 연결을 종료하지는 않습니다. 그러나 대부분의 오류가 연결을 할 수 없는 상태거나 서버와 메시지를 주고 받을 수 없는 상황에서 발생하므로 Transport에서 오류가 발생하면 해당 연결을 끊고 재연결을 시도하는 것이 좋습니다.

public class TransportError
{
  public enum Type
  {
    kNone,
    kStartingFailed,      // Transport 초기화에 실패했을 때 호출됩니다.
    kConnectingFailed,    // TCP 연결에 실패했을 때 호출됩니다.
    kInvalidSequence,     // 서버와 메시지 동기화에 실패했을 때 호출됩니다.
    kEncryptionFailed,    // 메시지 암호화에 실패했을 때 호출됩니다.
    kSendingFailed,       // 메시지를 보내는 과정에서 오류가 발생했을 때 호출됩니다.
    kReceivingFailed,     // 메시지를 받는 과정에서 오류가 발생했을 때 호출됩니다.
    kRequestFailed,       // HTTP의 요청에 실패했을 때 호출됩니다.
    kDisconnected         // 소켓 연결이 끊겼을 때 호출됩니다.
  }

  public Type type = Type.kNone;
  public string message = null;
}

FunapiSession 에 있는 TransportErrorCallback 은 다음과 같이 선언되어 있습니다.

void TransportErrorHandler (TransportProtocol protocol, TransportError type);

37.2.3. Transport 옵션

FunapiSession의 Connect 함수를 호출할 때 파라미터로 Transport의 옵션을 전달할 수 있습니다. 이 값이 null인 경우에는 default 값을 사용하게 됩니다. 사용할 프로토콜 (TCP, UDP,HTTP)에 따라 TransportOption 클래스를 생성하여 지정해야합니다. 예를들어 TCP의 경우, TcpTransportOption 클래스를 생성하여 전달합니다.

아래는 TransportOption 클래스가 정의된 코드입니다.

// Transport 옵션의 기본 클래스입니다.
// UDP 연결에만 이 기본 클래스를 사용할 수 있습니다.
public class TransportOption
{
    // 암호화 타입을 지정합니다.
    // 서버에서 암호화를 사용할 경우 이 값을 반드시 같은 타입 값으로 입력해야 합니다.
    // 암호화를 사용하지 않을 경우 이 값을 변경하지 마세요.
    public EncryptionType Encryption = EncryptionType.kDefaultEncryption;

    // 메시지에 sequence number를 붙여서 메시지의 유효성을 보장해주는 옵션입니다.
    // Session reliability 옵션을 사용하지 않고 메시지의 유효성만 보장하고 싶을 때 사용하면 됩니다.
    // Session reliability 옵션을 사용하면 이 옵션과 상관없이 메시지의 유효성을 보장해줍니다.
    // 이 옵션은 TCP, HTTP 프로토콜에서만 사용 가능합니다.
    public bool SequenceValidation = false;

    // 서버와 연결할 때 Timeout 시간을 정하고 싶다면 이 값을 수정하면 됩니다.
    // 지정한 시간 내에 연결이 되지 않으면 ConnectionTimeoutCallback 함수가 호출됩니다.
    public float ConnectionTimeout = 0f;
}

// TCP Transport 옵션 클래스입니다.
// TransportOption 값도 포함하면서 TCP에만 적용되는 옵션이 추가되어 있습니다.
public class TcpTransportOption : TransportOption
{
    // 이 값이 true일 경우 처음 서버에 연결할 때 연결 실패시 3-4회 정도 재연결을 시도합니다.
    // 같은 객체로 Stop 후 재연결시에는 연결에 실패해도 재시도하지 않습니다.
    public bool AutoReconnect = false;

    // 이 값을 true로 하면 네이글 알고리즘을 사용하지 않습니다.
    // 네이글 알고리즘은 효율적인 네트워크 사용을 위해 작은 크기의 패킷을 모아서 전송하는 기능입니다.
    // 기본 값은 false로 네이글 알고리즘을 사용합니다.
    public bool DisableNagle = false;

    // 클라이언트 핑 사용 옵션입니다.
    public bool EnablePing = false;
    // 핑 값을 로그로 표시할지 여부를 결정합니다.
    public bool EnablePingLog = false;
    // 핑 메시지를 보내는 간격에 대한 시간 값입니다.
    public int PingIntervalSeconds = 0;
    // 서버로부터 핑에 대한 응답을 기다리는 최대 시간 값입니다.
    // 이 시간 내에 핑 응답이 하나도 오지 않는다면 서버와의 연결이 끊긴 것으로 보고 Disconnect 처리됩니다.
    // 보통 PingIntervalSeconds 값의 3-4배 이상의 시간을 권장합니다.
    public float PingTimeoutSeconds = 0f;

    // 위의 핑 관련 옵션 값을 한 번에 설정할 수 있는 핑 사용 설정 함수입니다.
    public void SetPing (int interval, float timeout, bool enable_log = false);
}

// HTTP Transport 옵션 클래스입니다.
public class HttpTransportOption : TransportOption
{
    // HTTPs 사용 여부에 대한 옵션입니다.
    // 서버가 HTTPs일 경우 이 값을 true로 입력해야 합니다.
    public bool HTTPS = false;
    // UnityEngine.WWW 클래스 사용 여부에 대한 옵션입니다.
    public bool UseWWW = false;
}

플러그인에서는 HTTP 요청을 처리할 때 HttpWebRequest 클래스를 사용하고 있습니다. Windows에서 Unity Editor로 이 기능을 사용하는 경우, 간혹 에디터가 응답하지 않는 경우가 있습니다. (소켓을 명시적으로 닫아주지 않아서 발생하는 문제) 이 문제를 피하기 위해 UnityEngine 의 WWW 클래스를 사용할 수 있도록 UseWWW 옵션이 추가되었습니다. 에디터에서만 발생하는 현상이므로 기본적으로는 이 옵션을 false 로 두고 에디터에서 실행할 때만 WWW 클래스를 사용하는 것을 권장합니다.

Transport 옵션을 지정하는 샘플 코드는 예제 프로젝트 Tester.cs 파일의 makeOption 함수에 있습니다.

37.3. 메시지 전송 및 수신

37.3.1. 메시지 전송

메시지를 보내고 싶다면 FunapiSession 클래스의 SendMessage 함수를 호출하면 됩니다.

public void SendMessage (string msg_type, object message,
                         TransportProtocol protocol = TransportProtocol.kDefault,
                         EncryptionType enc_type = EncryptionType.kDefaultEncryption);

msg_type 은 메시지 타입을 나타내는 string 타입의 파라미터입니다. Protobuf로 메시지를 보내는 경우에는 string 타입 대신에 enum MessageType 타입을 사용할 수도 있습니다.

message 는 실제 데이터가 포함된 메시지로 object 타입입니다. JSON, Protobuf 메시지를 모두 보낼 수 있습니다.

protocol 은 지정하지 않을 경우 FunapiSession에 처음 등록(Connect) 된 프로토콜로 메시지가 전송됩니다. Transport가 여러 개 등록되어 있고 특정 프로토콜로 메시지를 보내고 싶다면 protocol 값을 지정하면 됩니다.

FunapiSession에 여러 개의 프로토콜이 연결되어 있고 메시지를 보낼 때 기본으로 사용되는 프로토콜을 별도로 지정하고 싶다면 FunapiSession의 DefaultProtocol 프로퍼티 값을 수정하면 됩니다.

session.DefaultProtocol = TransportProtocol.kHttp;

enc_type 은 암호화 타입 파라미터입니다. 암호화를 사용하는데 타입을 지정하지 않을 경우 기본 타입으로 암호화를 하게 되고 암호화를 사용하지 않을 경우에 이 값을 입력하면 오류가 발생합니다.

서버에서 하나의 프로토콜에 여러 종류의 암호화를 허용할 경우 메시지를 보낼 때 암호화 타입을 선택해서 보낼 수 있습니다. 암호화에 대한 좀 더 자세한 설명은 메시지 암호화 를 참고해 주세요.

37.3.1.1. JSON 메시지 보내기

Dictionary<string, object> message = new Dictionary<string, object>();
message["message"] = "hello world";
session.SendMessage("echo", message);

플러그인에서는 JSON 을 사용하기 위해 MiniJSON 라이브러리를 사용합니다. MiniJSON 의 데이터 타입은 Dictionary<string, object> 입니다. 필요한 경우 다른 JSON 라이브러리를 사용할 수도 있습니다. 이와 관련하여 자세한 설명은 JSON Helper 클래스 를 참고해 주세요.

37.3.1.2. Protobuf 메시지 보내기

PbufEchoMessage echo = new PbufEchoMessage();
echo.msg = "hello proto";
FunMessage message = FunapiMessage.CreateFunMessage(echo, MessageType.pbuf_echo);
session.SendMessage(MessageType.pbuf_echo, message);

Protobuf 메시지의 기본형은 FunMessage 입니다. 사용자 메시지는 extend 형태로 되어있습니다. extend 메시지 중 0 ~ 15번까지는 예약된 메시지입니다. 사용자 메시지는 16번 필드부터 사용이 가능합니다.

MessageType 은 서버에서 .proto 파일들을 빌드해서 메시지 DLL을 만들 때 함께 생성되는 enum 리스트입니다. 이를 통해 extend 메시지들의 숫자 대신 메시지 이름으로 사용할 수 있습니다.

Important

Unity에서 Protobuf-net 기반의 메시지를 사용하려면 추가적인 빌드 과정을 따라야 합니다. Unity에서 protobuf 사용하기 설명을 참고해주세요.

37.3.2. 메시지 수신

서버로부터 메시지를 받기 위해서는 메시지 콜백 함수를 등록해야 합니다. ReceivedMessageCallback 은 다음과 같이 선언되어 있습니다.

void ReceivedMessageHandler (string msg_type, object message);

데이터형이 object인 이유는 받는 메시지가 JSON일 수도 있고 Protobuf 메시지일 수도 있기 때문입니다. 등록한 핸들러에서 메시지 타입에 따라 message 객체를 JSON이나 Protobuf로 캐스팅해서 사용하면 됩니다.

아래는 메시지 핸들링에 대한 예제입니다. 전체 코드는 Tester.Session.cs 파일에서 확인하실 수 있습니다.

// 받은 메시지를 처리할 콜백을 등록합니다.
session.ReceivedMessageCallback += onReceivedMessage;

// 기다리는 메시지에 timeout을 정해서 알림을 받고 싶다면 이 콜백을 등록합니다.
// 메시지가 지정한 시간내에 오지 않으면 이 콜백 함수가 호출됩니다.
session.ResponseTimeoutCallback += onResponseTimedOut;

// 메시지를 처리하는 방식은 정해진 형식이 없으며 사용하기 편한 형태로 쓰시면 됩니다.
// 여기에서는 받은 메시지를 처리하기 위해 각각의 메시지를 Key-Value 방식으로 저장하겠습니다.
message_handler_["echo"] = onEcho;
message_handler_["pbuf_echo"] = onEchoWithProtobuf;

플러그인 내부에서 이미 Deserialize 된 메시지를 ReceivedMessageCallback 함수로 전달하기 때문에 메시지를 다시 Deserialize할 필요는 없습니다. 받은 object 메시지를 캐스팅만 해서 사용하면 됩니다.

아래는 message_handler_ 에 등록한 메시지 콜백 함수에 대한 구현입니다.

// JSON 메시지에 대한 처리 함수입니다.
void onEcho (object message)
{
    // 예제에서는 MiniJSON을 사용합니다.
    // 다른 JSON 라이브러리를 사용하고 싶다면 아래 JSON Helper 관련 설명을 참고해주세요.
    FunDebug.Assert(message is Dictionary<string, object>);
    // 이미 Deserialize 된 메시지를 문자열로 출력하기 위해 다시 Serialize 합니다.
    string strJson = Json.Serialize(message);
    FunDebug.Log("Received an echo message: {0}", strJson);
}

// Protobuf 메시지에 대한 처리 함수입니다.
void onEchoWithProtobuf (object message)
{
    // iFun Engine에서 사용하는 protobuf 메시지는 FunMessage를 기본으로 하고
    // 사용자 메시지는 FunMessage 안에 extend 형태로 추가해서 사용합니다.
    FunDebug.Assert(message is FunMessage);
    FunMessage msg = message as FunMessage;
    // extend 된 echo 메시지를 가져옵니다.
    object obj = FunapiMessage.GetMessage(msg, MessageType.pbuf_echo);
    if (obj == null)
        return;

    PbufEchoMessage echo = obj as PbufEchoMessage;
    FunDebug.Log("Received an echo message: {0}", echo.msg);
}

37.3.3. Response Timeout

서버로부터 기다리는 메시지에 대해 타임아웃을 정하고 싶다면 FunapiSession 클래스에 있는 SetResponseTimeout 함수를 사용해 원하는 메시지에 타임아웃 시간을 지정하면 됩니다.

// 기다리는 메시지 타입과 시간을 지정합니다. 등록되는 순간부터 시간이 적용됩니다.
// 'sc_login' 메시지가 10초 내에 오지 않을 경우 ResponseTimeoutCallback 함수가 호출됩니다.
session.SetResponseTimeout("sc_login", 10f);

Tip

같은 메시지 타입에 대해 중복 등록은 할 수 없습니다.

일회성 이벤트이므로 같은 메시지에 대해 타임아웃을 다시 설정하고 싶다면 ReceivedMessageCallback 함수로 기다리는 메시지를 받았거나 타임아웃 시간이 만료되어 ResponseTimeoutCallback 함수가 호출된 후에 SetResponseTimeout 함수로 다시 타임아웃을 지정해야 합니다.

ResponseTimeoutCallback 은 다음과 같이 선언되어 있습니다. SetResponseTimeout 함수로 전달했던 메시지 타입을 파라미터로 받습니다. 타임아웃 시간내에 서버로부터 해당 메시지를 받는다면 이 콜백 함수는 호출되지 않습니다.

public delegate void ResponseTimeoutHandler (string msg_type);

37.3.4. JSON Helper 클래스

기본적으로 플러그인은 MiniJSON을 사용합니다. 다른 JSON 라이브러리를 사용하고 싶다면 JsonAccessor 인터페이스 클래스를 상속받아 JSON 라이브러리를 핸들링하는 클래스를 만들어 FunapiMessage의 JsonHelper를 변경하면 됩니다.

JsonAccessor 클래스에는 없지만 필요한 함수가 있다면 JsonAccessor 파생 클래스에 추가해서 사용하시면 됩니다.

새로 만들어진 JsonAccessor 클래스는 아래와 같은 방법으로 등록하면 됩니다. 이후에 보내는 메시지와 받는 메시지는 모두 새로 등록된 JSON 클래스를 통해 Serialize나 Deserialize를 하게 됩니다.

NewJsonAccessor json_helper = NewJsonAccessor();
FunapiMessage.JsonHelper = json_helper;

Note

플러그인 버전 164에서 JsonAccessor 클래스의 인터페이스에 변경이 있었습니다. (2016년 8월 업데이트) 이전의 JsonAccessor 클래스를 상속받아 JsonHelper를 구현하셨다면 플러그인 업데이트시 추가된 인터페이스에 대해 추가 구현을 해주셔야 합니다.

37.4. 메시지 암호화

서버와 메시지를 주고 받을 때 메시지를 암호화할 수 있습니다.

37.4.1. 암호화 타입

암호화 타입의 종류는 아래와 같습니다.

public enum EncryptionType
{
  // 암호화를 사용하지만 특정 메시지를 암호화하지 않은 상태로 보내고 싶을 때
  // SendMessage의 파라미터로 이 값을 전달하면 됩니다.
  kDummyEncryption,

  // iFun Engine에서 제공하는 암호화 타입입니다.
  // 메시지를 주고 받을 때마다 키 값이 변경되어 안정적인 암호화 방식입니다.
  // Tcp 프로토콜에서만 사용 가능합니다.
  kIFunEngine1Encryption,

  // iFun Engine에서 제공하는 암호화 타입입니다.
  // 고정된 암호화 키를 사용합니다. 프로토콜에 상관없이 사용 가능합니다.
  kIFunEngine2Encryption,

  // ChaCha20 암호화 타입입니다.
  // Tcp 프로토콜에서만 사용 가능합니다.
  kChaCha20Encryption,

  // Aes 128 암호화 타입입니다.
  // Tcp 프로토콜에서만 사용 가능합니다.
  kAes128Encryption
}

암호화를 설정하면 모든 메시지를 암호화해서 보내게 되어 있는데 일부 메시지는 암호화를 하지 않고 그대로 보낼 수도 있습니다. 암호화를 사용하지만 특정 메시지를 암호화하지 않고 보내고 싶다면 SendMessage 의 암호화 타입으로 kDummyEncryption 을 선택하면 됩니다.

kIFunEngine1Encryption 타입은 메시지를 주고 받을 때마다 암호화 키가 변경됩니다. 동일한 메시지를 보내도 암호화 키 값이 매번 달라지므로 안정적인 암호화 방식입니다. kIFunEngine2Encryption 는 고정된 암호화 키를 사용하므로 kIFunEngine1Encryption 보다는 상대적으로 보안에 취약한 암호화 타입입니다.

Tip

IFunEngine1, ChaCha20, Aes128 암호화 방식은 TCP 프로토콜에서만 사용이 가능합니다.

ChaCha20Aes128 은 외부 라이브러리(sodium)를 사용하고 있습니다. 해당 라이브러리 파일은 Plugins 폴더에 있습니다.

37.4.2. 암호화 사용

TCP 의 경우 아무런 설정을 하지 않아도 서버에서 암호화 설정을 하면 클라이언트에서는 이를 따르도록 되어 있습니다. 처음 서버에 접속하면 암호화에 대한 동기화가 이루어지는데 서버에서 해당 프로토콜의 암호화 타입 목록을 전달하면 클라이언트는 서버에서 정한 암호화 방식을 사용하게 됩니다.

만약 암호화 타입이 여러 개라면 첫 번째 암호화 타입을 기본 암호화 타입으로 사용하게 됩니다. 메시지를 보낼 때 특정 암호화 타입을 지정하지 않는다면 이 때 지정된 기본 타입으로 암호화를 하게 됩니다. 기본 암호화 타입을 별도로 지정하고 싶다면 FunapiSession.Connect 함수를 호출할 때 TransportOption의 Encryption 값을 정해주면 됩니다.

TcpTransportOption option = new TcpTransportOption();
option.Encryption = EncryptionType.kIFunEngine1Encryption;

만약 클라이언트의 암호화 타입이 서버에서 사용하지 않는 타입일 경우 오류가 발생하고 메시지를 주고 받을 수 없게 됩니다.

UDP 와 HTTP 의 경우에는 접속 후 서버와 암호화 타입을 동기화하는 과정이 없으므로 클라이언트에서 사용할 암호화 타입을 지정해주어야 합니다. 아이펀 엔진에서 제공하는 암호화 타입 중 UDP 와 HTTP 가 사용할 수 있는 암호화 타입은 IFunEngine2Encryption 밖에 없습니다. UDP 와 HTTP 에서 암호화를 사용하고 싶다면 이 타입을 TransportOption의 Encryption 에 지정하면됩니다.

TransportOption option = new TransportOption();
option.Encryption = EncryptionType.kIFunEngine2Encryption;

Note

GitHub 에 배포된 클라이언트 플러그인은 IFunEngine1Encryption과 IFunEngine2Encryption 타입의 암호화 기능이 포함되지 않은 무료 버전입니다. ChaCha20과 Aes128 타입은 사용 가능합니다. 유료 고객 의 경우 Funapi support 로 요청 메일을 보내주시면 암호화 타입이 모두 포함된 플러그인 소스를 보내드립니다.

37.5. 연결 종료 및 재연결

37.5.1. 연결 종료

서버와의 연결을 종료하기 위해서는 FunapiSession 의 Stop 함수를 호출하면 됩니다.

session.Stop();

파라미터가 없는 Stop 함수는 연결된 모든 Transport 의 연결을 끊습니다. 특정 Transport 의 연결을 끊고 싶다면 파라미터로 프로토콜 타입을 전달하면 됩니다.

session.Stop(TransportProtocol.kTcp);

Stop 함수는 비동기 방식으로 Transport 의 연결을 끊습니다. Stop 함수가 호출됐을 때 연결을 바로 종료하지 않는 이유는 Transport 가 서버에 연결을 시도하는 중일 때 이를 기다리거나 보내지 못한 메시지가 있을 때 버퍼에 남아 있는 메시지를 보내고 연결을 끊기 위해서입니다.

Note

Transport 가 서버에 연결을 시도할 때 기다리는 이유는 연결 도중에 Stop이 호출될 경우 IL2CPP 관련 버그로 인해 iOS 에서 크래쉬가 발생하기 때문입니다.

연결 종료전 미전송 메시지를 보내고 연결을 종료하는 기능은 TCP 만 지원합니다. TCP 연결의 경우 SendMessage는 호출되었으나 아직 전송하지 못하고 메시지가 남아 있는 경우, 남은 메시지를 모두 보낸 후 연결을 끊습니다. 이를 통해 Stop 을 호출하기 직전에 로그아웃 등의 메시지를 보내도 메시지의 전송을 보장합니다.

일부 기다림 없이 종료되는 경우도 있는데 서버에서 Session 을 닫았을 경우, 서버간 이동을 위해 연결을 종료해야 할 경우, 앱이 종료되는 경우에는 대기 없이 바로 연결을 끊습니다.

37.5.2. 연결 종료 알림

37.5.2.1. Transport Event

Transport 의 연결이 종료되면 Transport Event 로 kStopped 가 전달됩니다. 이 이벤트는 오류로 인해 연결이 끊겼거나 정상적으로 연결이 종료되었거나 상관없이 Transport의 연결이 끊기면 항상 발생합니다.

Transport 의 연결이 의도치 않게 끊겼을 경우에는 Transport Event 로 kDisconnected 이벤트가 전달됩니다. Ping 을 사용할 경우 서버와의 연결을 체크해서 일정 시간 이상 서버에서 응답이 없을 경우에도 kDisconnected 이벤트가 발생합니다. Ping 을 사용하지 않을 경우 이 이벤트만으로 다양한 모바일 환경에서의 연결 끊김을 체크하기에는 무리가 있습니다. 기기의 종류나 통신망의 종류에 따라 연결의 끊김을 감지하기까지 시간이 오래 걸리는 경우도 있기 때문입니다. kDisconnected 이벤트로 서버와의 연결 상태를 체크하고 싶다면 Ping 을 사용하시길 권합니다.

Tip

HTTP 의 경우에는 프로토콜 특성상 kDisconnected 이벤트가 발생하지 않으며 Ping 은 TCP 프로토콜에서만 사용 가능합니다.

Transport 의 이벤트는 FunapiSession 의 TransportEventCallback 을 등록해서 받을 수 있습니다.

37.5.2.2. Session Event

Session 에 연결된 모든 Transport 의 연결이 끊기면 Session Event 로 kStopped 이벤트가 전달됩니다.

Session kClosed 이벤트는 Transport 의 연결이 끊겼다고 발생하는 것이 아니라 Session Timeout 이 되거나 서버에서 명시적으로 Session 을 닫았을 경우에만 발생합니다. 서버로부터 Session 이 닫혔다는 메시지를 받으면 모든 Transport 의 연결을 끊고 kClosed 이벤트를 전달하게 됩니다.

Session 이벤트는 FunapiSession 의 SessionEventCallback 을 등록해서 받을 수 있습니다.

37.5.3. 재연결

서버와의 연결을 종료한 후 재연결을 할 때에는 FunapiSession.Connect 함수를 사용합니다. 연결할 Transport 의 프로토콜 타입만 파라미터로 전달하면 됩니다.

public void Connect (TransportProtocol protocol)

TransportOption 파라미터가 포함 된 Connect 함수를 사용해서 재연결을 할 수도 있지만 한 번 등록된 Transport 의 옵션은 변경할 수 없으며 새로운 TransportOption 을 전달하더라도 무시됩니다. 그러므로 재연결시에는 프로토콜만 지정해서 Connect 함수를 호출하는 것이 좋습니다.

TCP 의 경우 AutoReconnect 옵션이 true 로 설정되어 있더라도 재연결시에는 연결에 실패해도 재연결을 시도하지 않습니다. AutoReconnect 옵션은 첫 연결에서만 적용됩니다.

37.6. Ping 사용하기

네트워크의 연결 상태를 확인하기 위해 터미널에서 사용하는 ping 이라는 명령어가 있습니다. 플러그인에도 이와 비슷하게 동작하는 Ping 함수가 있습니다. 서버와의 연결이 유지되고 있는지 확인하는 용도로 사용할 수 있습니다.

예측하기 힘든 모바일 네트워크 환경에서 Ping 함수를 사용하여 지속적으로 연결 상태를 검사할 수 있습니다. 이를 통하여 연결이 끊겼는지를 더 빠르게 파악하고 대응할 수 있습니다.

Tip

이 기능은 TCP 프로토콜에서만 사용 가능합니다.

37.6.1. Ping 설정

핑은 FunapiSession.Connect 함수를 호출하기 전에 Transport 옵션으로 설정할 수 있습니다. Ping 기능을 사용하려면 아래와 같이 SetPing 함수를 호출하거나 각각의 값을 입력해주면 됩니다.

TcpTransportOption tcp_option = new TcpTransportOption();

// 이 함수만 호출하면 핑 설정이 완료됩니다.
tcp_option.SetPing(3, 20, true);

...

// 각각의 값을 따로 입력하고 싶다면 아래와 같이 하면 됩니다.

// 이 값을 true로 주면 Ping 기능을 사용할 수 있습니다.
// EnablePing의 기본 값은 false 입니다.
tcp_option.EnablePing = true;
// 핑 값을 로그로 보고 싶다면 이 값을 true로 주면 됩니다.
tcp_option.EnablePingLog = true;
// 핑 메시지를 보내는 간격을 지정합니다. (초단위)
tcp_option.PingIntervalSeconds = 3;
// 서버로부터 핑 응답을 기다리는 최대 시간을 지정합니다. (초단위)
// 이 시간 이내에 응답이 하나도 없을 경우 연결을 끊고 Disconnect 처리를 하게 됩니다.
tcp_option.PingTimeoutSeconds = 20;

핑 타임아웃 시간 내에 서버로부터 응답이 하나도 없을 경우 연결을 끊고 Disconnect 처리를 하게 됩니다. Disconnect 되면 FunapiSession의 TransportEventCallback으로 kDisconnected가 전달됩니다.

서버에서 Ping 기능을 활성화 하는 법은 세션 Ping(RTT) 에서 확인할 수 있습니다.

37.7. 서버간 이동

서버의 요청에따라 접속 중인 서버의 연결을 끊고 새로운 서버로 접속해야 하는 경우가 있을 수 있습니다. 이 과정은 플러그인 내부에서 이루어지며 서버 쪽의 요청으로 이루어지는 동작이므로 클라이언트에서 서버에 이동을 요청할 수는 없습니다.

37.7.1. 서버간 이동 상태 확인

서버간 이동이 시작되면 SessionEventCallback 을 통해서 서버간 이동에 관한 진행 상태가 전달됩니다. 아래는 콜백을 통해서 받게 되는 이벤트 타입의 종류입니다.

public enum SessionEventType
{
    ...
    kRedirectStarted,       // 서버간 이동을 시작합니다.
    kRedirectSucceeded,     // 서버간 이동을 완료했습니다.
    kRedirectFailed         // 서버간 이동에 실패했습니다.
};

서버간 이동이 시작되면 kRedirectStarted 이벤트를 전달한 후 현재 연결된 서버와의 접속을 종료하고 새로운 서버로 연결을 시작합니다. 서버간 이동이 완료될 때까지는 Redirect와 관련된 이벤트를 제외한 Session이나 Transport 관련 이벤트는 발생되지 않습니다. 연결 도중 오류가 발생해도 디버그 로그만 출력할 뿐 어떠한 이벤트도 발생되지 않습니다. 서버간 이동이 완료되면 kRedirectSucceededkRedirectFailed 이벤트가 전달됩니다.

37.7.2. Transport 옵션

서버를 이동할 때 접속 중인 서버와 이동하는 서버간의 프로토콜 종류나 개수가 다를 수도 있습니다. 이럴 경우 Transport를 새로 만들게 되는데 이 때 Transport의 옵션을 정해줄 수 있는 콜백 함수가 있습니다.

public delegate TransportOption TransportOptionHandler (string flavor, TransportProtocol protocol);
public event TransportOptionHandler TransportOptionCallback;

flavor 는 서버의 종류를 나타냅니다. 이 타입은 서버에서 정해주는 문자열 값입니다. 서버로부터 이동 메시지를 받으면 이동할 서버의 프로토콜 구성에 따라 이 콜백 함수가 호출됩니다. 해당 서버의 종류와 프로토콜 타입에 따라 옵션을 구성해서 반환하면 됩니다.

이동하는 서버와 동일한 프로토콜을 사용하거나 옵션을 정하지 않고 기본 옵션으로 연결해도 되는 경우에는 굳이 이 이벤트 콜백을 등록할 필요는 없습니다.

37.8. 멀티캐스팅과 채팅

플러그인의 멀티캐스팅 기능을 사용하면 원하는 채널에 접속해서 채널의 모든 유저들과 메시지를 주고 받을 수 있습니다. FunapiSession 객체를 이용해 멀티캐스팅 역할을 하는 서버와 연결을 하고 메시지를 전송합니다. FunapiMulticastClient 객체를 만들면서 FunapiSession 객체를 전달하여 멀티캐스팅 기능을 사용할 수 있습니다.

FunapiMulticastClient 는 같은 공간(채널)에 있는 모든 유저에게 메시지를 보내고자 할 때 사용할 수 있습니다. _bounce 옵션을 true 로 주면 보내는 주체인 ‘나’도 그 메시지를 받을 수 있습니다.

채팅에 특화된 클래스인 FunapiChatClient 는 FunapiMulticastClient 를 상속받아 만들어진 파생 클래스입니다. FunapiChatClient 는 텍스트만 주고 받는 클래스로 플레이어간 채팅에 사용하기 적합합니다.

멀티캐스팅은 TCP 를 사용 하며, Encoding 은 연결된 FunapiSession에서 사용하는 방식을 따릅니다. JSON과 Protobuf 를 사용할 수 있습니다.

Important

멀티캐스팅은 하나의 Session마다 하나의 객체만 연결할 수 있습니다. 두 개 이상의 멀티캐스팅 객체를 연결하게 되면 Session에서 멀티캐스트 메시지를 받았을 때 어느 객체에게 메시지를 전달해야 하는지 알 수 없습니다. FunapiMulticastClient 클래스와 FunapiChatClient 클래스도 역시 동시에 두 개를 같은 Session으로 연결할 수 없습니다.

37.8.1. 멀티캐스팅 인터페이스

FunapiMulticastClient 에는 다음과 같은 인터페이스가 존재합니다.

// FunapiMulticastClient 생성자입니다.
// FunapiSession와 주고 받을 메시지의 Encoding을 입력받습니다.
public FunapiMulticastClient (FunapiSession session, FunEncoding encoding)

// 내 id(name)입니다.
// sender를 지정하면 채널 입/퇴장이나 메시지를 보낼 때 sender를 함께 전송합니다.
// 나를 포함한 채널의 모든 유저가 해당 메시지를 보낸 이를 확인할 수 있습니다.
public string sender { set; }

// 서버와 연결되어 있는지 확인하기 위한 property 입니다.
// 서버와 연결이 되어 있다면 true를 반환합니다.
public bool Connected;

// 채널 목록을 요청하는 함수입니다.
// 채널 목록과 함께 채널에 있는 유저 수도 함께 전달됩니다.
// 요청에 성공하면 ChannelListCallback에 등록한 함수가 호출됩니다.
public void RequestChannelList ();

// channel_id 가 입장한 채널의 id 인지 확인하는 property 입니다.
// 입장한 채널이면 true를 반환합니다.
public bool InChannel (string channel_id);

// 채널에 입장할 때 사용하는 함수입니다.
// channel_id 는 입장할 channel id 입니다. 존재하지 않는 채널이면 서버에서 생성합니다.
// handler는 메시지를 전달받으면 호출되는 함수입니다.
// 이미 channel_id에 해당하는 채널에 입장했거나 서버와 연결되어 있지 않으면 false 를 반환합니다.
public bool JoinChannel (string channel_id, ChannelMessage handler);

// 채널을 나갈 때 사용하는 함수입니다.
// channel_id는 나갈 channel id 입니다.
// 입장한 채널이 없거나 서버와 연결되어 있지 않으면 false 를 반환합니다.
public bool LeaveChannel (string channel_id);

// 채널에 메시지를 전송할 때 사용하는 함수입니다.
// 이 함수는 protobuf로 메시지를 전송할 때 사용합니다.
public bool SendToChannel (FunMulticastMessage mcast_msg);

// 이 함수는 json으로 메시지를 전송할 때 사용합니다.
public bool SendToChannel (object json_msg);

// 채널에 입/퇴장을 알려주는 Handler의 delegate 입니다.
// 채널의 입/퇴장 알림을 받고 싶다면 ChannelNotify의 event 객체인
// JoinedCallback이나 LeftCallback을 등록하면 됩니다.
public delegate void ChannelNotify(string channel_id, string sender);

// 채널로부터 메시지를 받으면 호출되는 Handler의 delegate 입니다.
// 이 delegate와 동일한 타입으로 함수를 만들고 JoinChannel() 함수의 두 번째 인자로 전달합니다.
// channel_id 는 메시지를 전달받은 채널 id 입니다.
// body는 전달받은 메시지이며 Encoding에 따라서 JSON이나 Protobuf인 FunMulticastMessage가 전달됩니다.
public delegate void ChannelMessage(string channel_id, string sender, object body);

37.8.2. 멀티캐스팅 예제

FunapiMulticastClient 로 멀티캐스팅을 처리하기 위한 사전 준비로서, 먼저 FunapiSession 을 만들고 Tcp로 Connect 합니다.

string kServerIp = "127.0.0.1";

// 우선 FunapiSession 을 만듭니다.
// 멀티캐스팅은 session reliability 와 상관없이 동작합니다.
// 예제의 단순함을 위해 여기서는 이 기능을 사용하지 않겠습니다.
session = FunapiSession.Create(kServerIp, false);

// Transport의 이벤트를 받기 위한 콜백함수를 등록합니다.
session.TransportEventCallback += onTransportEvent;

// 서버에 접속합니다.
// Transport의 옵션 설정은 생략하겠습니다.
session.Connect(TransportProtocol.kTcp, FunEncoding.kJson, 8012);

이제 메시지를 전송하기 위해 FunapiMulticastClient 를 만들겠습니다.

채널 입/퇴장을 알려주는 콜백 함수를 등록할 수 있습니다. 나를 포함해서 채널에 들어오고 나가는 모든 유저들의 입/퇴장을 알려줍니다.

JoinedCallback 유저가 채널에 입장하면 호출되는 함수
LeftCallback 유저가 채널에서 나가면 호출되는 함수
ErrorCallback 오류가 발생하면 에러코드를 전달해주는 함수
// FunapiMulticastClient를 만듭니다.
// 위에서 만든 session과 session과 같은 encoding 타입을 전달합니다.
FunapiMulticastClient multicast = new FunapiMulticastClient(session, FunEncoding.kJson);

// 내 아이디를 입력합니다.
multicast_.sender = "my name";

// 채널 목록을 받았을 때 호출되는 콜백입니다.
multicast_.ChannelListCallback += delegate (object channel_list) {
    onMulticastChannelList(encoding, channel_list);
};

// Player가 채널에 입장하면 호출되는 콜백입니다.
multicast_.JoinedCallback += delegate(string channel_id, string sender) {
    DebugUtils.Log("JoinedCallback called. player:{0}", sender);
};

// Player가 채널에서 퇴장하면 호출되는 콜백입니다.
multicast_.LeftCallback += delegate(string channel_id, string sender) {
    DebugUtils.Log("LeftCallback called. player:{0}", sender);
};

// 에러가 발생했을 때 알림을 받는 콜백입니다.
// 에러 종류는 enum FunMulticastMessage.ErrorCode 타입을 참고해주세요.
multicast_.ErrorCallback += delegate(FunMulticastMessage.ErrorCode code) {
    // error
};

채널에 입장하기 위해서 JoinChannel() 함수를 호출합니다. 해당 채널로부터 메시지가 전송되면 onMulticastChannelReceived 함수가 호출됩니다.

동시에 여러 개의 채널에 입장하려면 입장을 원하는 채널마다 JoinChannel() 함수를 호출하면 됩니다.

// 입장할 채널 id 를 입력합니다.
// 채널이 존재하지 않으면 JoinChannel() 함수를 호출할 때 채널이 생성됩니다.
string channel_id = "test_channel";

// 채널에 입장하기 위해 JoinChannel() 함수를 호출합니다.
// 해당 채널로부터 메시지가 전송되면 onMulticastChannelReceived 함수가 호출됩니다.
multicast.JoinChannel(channel_id, onMulticastChannelReceived);

// 채널에 메시지 전송
PbufHelloMessage hello_msg = new PbufHelloMessage();
hello_msg.message = "multicasting message";

PbufHelloMessage 는 서버의 {project}_messages.proto 파일에 존재합니다. funapi_initiator 로 처음 프로젝트를 생성하면 기본적으로 추가되어 있습니다. PbufHelloMessage는 예제를 위해 기본 생성된 것이며 MulticastServer 에서는 proto message 의 이름이나 field 를 제한하지 않습니다. 따라서 필요한 이름으로 proto message 를 생성하고 사용할 field 들을 자유롭게 정의하시면 아이펀 엔진에서는 이를 알아서 클라이언트에게 전송합니다. 이것은 JSON 도 마찬가지입니다.

아래는 서버의 {project}_messages.proto 파일에 있는 기본 내용입니다.

message PbufAnotherMessage {
  optional string msg = 1;
}

extend FunMessage {
  optional PbufEchoMessage pbuf_echo = 16;
  optional PbufAnotherMessage pbuf_another = 17;
}


import "funapi/service/multicast_message.proto";

message PbufHelloMessage
{
  optional string message = 1;
}
extend FunMulticastMessage
{
  optional PbufHelloMessage pbuf_hello = 9;
}

아래 onMulticastChannelReceived 의 protobuf 예제 코드에서 MulticastMessageType.pbuf_hello 라는 메세지는, 플러그인에 기본적으로 추가되어 있지는 않습니다. 서버를 빌드할 때 proto 파일도 함께 빌드할 수 있는데 이때 extend FunMulticastMessage 에 입력한 PBufHelloMessage 의 field 이름과 extend 번호를 자동으로 인식하여 enum 을 만들게 됩니다. 이에 대한 자세한 설명은 Unity에서 protobuf 사용하기 를 참고해주세요.

private void onMulticastChannelReceived (string channel_id, string sender, object body)
{
    if (multicast_.encoding == FunEncoding.kJson)
    {
        // 채널이 맞는지 확인합니다.
        string channel = FunapiMessage.JsonHelper.GetStringField(body, "_channel");
        FunDebug.Assert(channel != null && channel == channel_id);

        // 메시지를 전송할 때 "message" 필드에 전송할 메시지를 담았습니다.
        // 따라서 수신한 메시지는 mcast_msg["message"] 에 있습니다.
        string message = FunapiMessage.JsonHelper.GetStringField(body, "_message");

        // 여기서는 간단하게 로그를 출력하겠습니다.
        DebugUtils.Log("Received a multicast message from the '{0}' channel.\nMessage: {1}",
                       channel_id, message);
    }
    else
    {
        // 수신한 메시지를 Protobuf인 FunMulticastMessage로 처리합니다.
        FunMulticastMessage mcast_msg = body as FunMulticastMessage;

        // MulticastMessageType 은 서버를 빌드할 때 proto 파일도 함께 빌드하면 자동 생성됩니다.
        // 이 예제에서는 PbufHelloMessage를 사용하므로 이것의 field 이름인 pbuf_hello를 사용합니다.
        object obj = FunapiMessage.GetMulticastMessage(mcast_msg, MulticastMessageType.pbuf_hello);
        PbufHelloMessage hello_msg = obj as PbufHelloMessage;
        if (hello_msg == null)
            return;

        // 메시지를 전송할 때 PbufHelloMessage의 message field에 메시지를 저장합니다.
        // 따라서 hello_msg.message 에 전달된 메시지가 담겨 있습니다.
        DebugUtils.Log("Received a multicast message from the '{0}' channel.\nMessage: {1}",
                       channel_id, hello_msg.message);
    }
}

메시지를 전송하려면 SendToChannel() 함수를 호출합니다.

// channel id를 입력합니다.
string channel_id = "test_channel";

if (multicast_.encoding == FunEncoding.kJson)
{
    // JSON으로 메시지를 전송합니다.
    Dictionary<string, object> mcast_msg = new Dictionary<string, object>();

    // _channel 필드에 channel id를 입력합니다.
    mcast_msg["_channel"] = channel_id;

    // 보낸 메시지를 나도 받고 싶다면 _bounce 필드값을 true로 설정합니다.
    mcast_msg["_bounce"] = true;

    // 메시지를 mcast_msg["message"] 에 담겠습니다.
    // 이는 자유롭게 변경하셔도 되며 필요하다면 다른 필드를 추가하셔도 됩니다.
    mcast_msg["message"] = "multicast test message";

    // 다른 필드 추가
    // mcast_msg["something"] = "...";

    // 멀티캐스팅 역할을 하는 서버에 메시지를 전송합니다.
    multicast.SendToChannel(mcast_msg);
}
else
{
    // protobuf로 메시지를 전송합니다.
    // PbufHelloMessage 파일은 서버의 {project}_messages.proto 파일에 정의 되어 있습니다.
    // 기본적으로 포함된 proto message인데 자유롭게 이름과 field 등을 정의하셔도 됩니다.
    PbufHelloMessage hello_msg = new PbufHelloMessage();

    // message field에 전송할 메시지를 담겠습니다.
    hello_msg.message = "multicast test message";

    // 멀티캐스팅 메시지를 전송하기 위해서 FunMulticastMessage 를 생성합니다.
    // 이 부분은 멀티캐스팅 역할을 하는 서버에서 필요한 proto message 이므로 반드시 필요한 부분입니다.
    FunMulticastMessage mcast_msg = new FunMulticastMessage();
    // channel field 에 channel id 를 입력합니다.
    mcast_msg.channel = channel_id;

    // 보낸 메시지를 나도 받고 싶다면 bounce 필드 값을 true로 설정합니다.
    mcast_msg.bounce = true;

    // protobuf 에서 제공하는 Extensible 인터페이스를 이용하여 PbufHelloMessage를 추가합니다.
    Extensible.AppendValue(mcast_msg, (int)MulticastMessageType.pbuf_hello, hello_msg);

    // 멀티캐스팅 역할을 하는 서버에 메시지를 전송합니다.
    multicast_.SendToChannel(mcast_msg);
}

채널에서 나갈 때는 LeaveChannel() 함수를 호출합니다.

// 채널 나가기
string channel_id = "test_channel";
multicast.LeaveChannel(channel_id);

37.8.3. 채팅 인터페이스

FunapiChatClient 에는 다음과 같은 인터페이스가 존재하며, FunapiMulticastClient 와의 차이점은 메시지를 object 객체로 받지 않고 string 만 받는다는 것입니다.

// FunapiChatClient 생성자입니다.
// FunapiSession과 주고 받을 메시지의 Encoding을 입력받습니다.
public FunapiChatClient (FunapiSession session, FunEncoding encoding);

// 채널에 입장할 때 사용하는 함수입니다.
// chat_channel은 입장할 channel id 입니다. 존재하지 않는 채널이면 서버에서 생성합니다.
// my_name은 채팅에서 사용할 나의 이름입니다. FunapiMulticastClient의 sender에 값을 저장하게 됩니다.
// handler는 메시지를 전달받으면 호출되는 함수입니다.
// 이미 channel_id 에 해당하는 채널에 입장했거나 서버와 연결되어 있지 않으면 false를 반환합니다.
public bool JoinChannel (string chat_channel, string my_name, OnChatMessage handler);

// my_name 파라미터가 없는 채널에 입장할 때 사용하는 함수입니다.
// FunapiMulticastClient의 sender 값을 지정했다면 채널에 입장할 때 이 함수를 사용해도 좋습니다.
public bool JoinChannel (string channel_id, OnChatMessage handler);

// 채널을 나갈 때 사용하는 함수입니다.
// 채널에서 나갈 때는 FunapiMulticastClient의 LeaveChannel 함수를 호출하면 됩니다.
public bool LeaveChannel (string channel_id);

// 채널에 메시지를 전송할 때 사용하는 함수입니다.
// chat_channel은 메시지를 전송할 channel 의 id 입니다.
// text는 전송할 메시지입니다.
public bool SendText (string chat_channel, string text);

// 채널로부터 메시지를 받으면 호출되는 Handler의 delegate 입니다.
// 이 delegate와 동일한 타입으로 함수를 만들고 JoinChannel() 함수의 인자로 전달합니다.
public delegate void OnChatMessage(string channel_id, string sender, string text);

37.8.4. 채팅 예제

FunapiChatClient 는 멀티캐스팅 기능을 활용하여 만든 클래스로써 사용법은 FunapiMulticastClient 와 매우 유사합니다. 따라서 이곳에서는 간단하게 FunapiChatClient 의 사용법을 익히도록 하겠습니다.

예제에서는 Encoding 을 Json 으로 하겠습니다.

// FunapiSession을 생성합니다.
FunapiSession session = FunapiSession.Create(kServerIp, false);
session.TransportEventCallback += onTransportEvent;

// 서버에 연결합니다.
session.Connect(TransportProtocol.kTcp, FunEncoding.kJson, 8012);

...

// FunapiChatClient 객체를 생성합니다.
FunapiChatClient chat = new FunapiChatClient(session, FunEncoding.kJson);

// 입장하는 플레이어의 이름을 입력합니다.
chat_.sender = "my name";

// 채널 입장
string channel_id = "test_channel";
chat.JoinChannel(channel_id, onMulticastChannelReceived);

메시지를 전달받으면 onMulticastChannelReceived() 함수가 호출됩니다.

// 메시지를 전달받으면 호출됩니다.
// FunapiMulticastClient 와는 달리 encoding에 따라 message를 처리하지 않아도 됩니다.
// 메시지 보낸 사람인 sender와 전송된 메시지인 text를 처리하시면 됩니다.
private void onMulticastChannelReceived (string chat_channel, string sender, string text)
{
    // 여기서는 간단하게 로그를 출력하겠습니다.
    DebugUtils.Log("Received a chat channel message.\nChannel={0}, sender={1}, text={2}",
                   chat_channel, sender, text);
}

SendText() 함수를 이용하여 채널에 메시지를 전송합니다.

// 채널에 메시지 전송
string channel_id = "test_channel";
string message = "Hello World";
chat.SendText(channel_id, message);

채널에서 나갈 때는 LeaveChannel() 함수를 사용합니다.

// 채널 나가기
string channel_id = "test_channel";
chat.LeaveChannel(channel_id);

37.9. 공지사항 확인하기

엔진에서 제공하는 공지서버를 사용하고 있다면 클라이언트 플러그인을 통해 공지서버로부터 공지사항 목록을 받을 수 있고 언제든지 원하는 시점에 공지사항을 업데이트할 수 있습니다.

FunapiAnnouncement 클래스로 서버에 공지사항 목록을 요청할 수 있습니다. 아래는 FunapiAnnouncement 클래스를 사용해서 서버에 공지사항을 요청하고 콜백 함수에서 공지사항 목록을 파싱하는 샘플 코드입니다. 메시지는 타입은 JSON 입니다.

FunapiAnnouncement announcement = new FunapiAnnouncement();

void Init ()
{
    // 공지사항은 웹 서비스입니다.
    // 서버에서 공지사항 용도로 열어 둔 ip와 port를 주소로 요청합니다.
    announcement.Init("http://127.0.0.1:8080");
    announcement.ResultCallback += OnAnnouncementResult;

    // 이 함수를 호출하면 목록을 가져오거나 이미 있을경우 갱신해줍니다.
    announcement.UpdateList();
}

void OnAnnouncementResult (AnnounceResult result)
{
    // 결과가 Success가 아닐경우
    if (result != AnnounceResult.kSuccess)
        return;

    // 메시지는 JSON 타입이며 MiniJSON을 사용합니다.
    if (announcement.ListCount > 0)
    {
        for (int i = 0; i < announcement.ListCount; ++i)
        {
            // 각각의 목록에 대해 정보를 가져올 수 있습니다.
            Dictionary<string, object> list = announcement.GetAnnouncement(i);
            string buffer = "";

            foreach (var item in list)
                buffer += item.Key + ": " + item.Value + "\n";

            Debug.Log("announcement >> " + buffer);
        }
    }
}

공지사항에 들어가는 항목들은 정해져 있지 않습니다. 서버 관리자가 필요한 항목들을 정의해서 사용하면 됩니다. 단, 기존에 사용중인 필드들을 변경할 경우 플러그인 코드도 함께 수정해야 할 수도 있으므로 기존 필드를 변경해야 할 경우엔 Funapi support 로 문의해주시기 바랍니다.

공지사항에 이미지가 포함되어 있을 경우 공지사항을 다 받은 후에 순차적으로 다운로드 하여 image_url 에 들어 있는 파일을 FunapiUtils.GetLocalDataPath/ + "announce/" 경로 아래에 저장합니다. 모든 다운로드가 끝나면 OnAnnouncementResult 콜백 함수가 호출됩니다. 다운로드 할 이미지가 많을 경우 콜백 함수 호출까지 시간이 오래걸릴 수도 있습니다.

37.10. 서버 점검 메시지

서버가 점검중이라면 클라이언트에서 보낸 메시지는 무시되고 서버는 메시지를 받을 때마다 클라이언트에 점검 안내 메시지를 보냅니다. 서버 연결 후 서버로 아무런 메시지도 보내지 않는다면 서버에서 점검 안내 메시지를 받을 수 없습니다.

점검안내 메시지는 처리는 메시지 수신 콜백이 아닌, FunapiSession 의 MaintenanceCallback 함수로 전달됩니다. 서버 점검 메시지를 처리하고 싶다면 MaintenanceCallback 함수를 등록하고 콜백 함수에서 필요한 작업을 수행하면 됩니다.

// 서버로부터 점검 안내 메시지를 받았을 때 이 콜백함수가 호출됩니다.
session.MaintenanceCallback += onMaintenanceMessage;

서버 점검 메시지를 받으면 아래와 같은 방식으로 파싱해서 사용하면 됩니다. 메시지 내용은 점검 시작 일시, 종료 일시, 문자열 메시지로 이루어져 있습니다.

date_start string 서버 점검 시작 일시
date_end string 서버 점검 종료 일시
messages string 메시지
if (encoding == FunEncoding.kJson)
{
  JsonAccessor json_helper = FunapiMessage.JsonHelper;
  FunDebug.Log("Maintenance message\nstart: {0}\nend: {1}\nmessage: {2}",
               json_helper.GetStringField(message, "date_start"),
               json_helper.GetStringField(message, "date_end"),
               json_helper.GetStringField(message, "messages"));
}
else if (encoding == FunEncoding.kProtobuf)
{
  FunMessage msg = message as FunMessage;
  object obj = FunapiMessage.GetMessage(msg, MessageType.pbuf_maintenance);
  if (obj == null)
    return;

  MaintenanceMessage maintenance = obj as MaintenanceMessage;
  FunDebug.Log("Maintenance message\nstart: {0}\nend: {1}\nmessage: {2}",
               maintenance.date_start, maintenance.date_end, maintenance.messages);
}

전체 코드는 Tester.Session.cs 파일에서 확인할 수 있습니다.

37.11. 리소스 파일 다운로드

서버에서 클라이언트 리소스 서비스를 사용한다면 클라이언트에서 별도의 바이너리 업데이트 없이도 클라이언트의 리소스를 업데이트할 수 있습니다.

이 기능을 사용하려면 FunapiHttpDownloader 클래스를 사용해야 합니다. 아래는 FunapiHttpDownloader 를 생성하고 콜백 함수들을 등록하는 예제 코드입니다. 전체 코드는 Tester.Download.cs 파일에서 확인하실 수 있습니다.

// FunapiHttpDownloader 객체를 생성합니다.
FunapiHttpDownloader downloader = new FunapiHttpDownloader();

// 필요한 콜백을 등록합니다.
// ReadyCallback과 FinishedCallback은 필수로 등록해야 합니다.
downloader.VerifyCallback += OnDownloadVerify;
downloader.ReadyCallback += OnDownloadReady;
downloader.UpdateCallback += OnDownloadUpdate;
downloader.FinishedCallback += OnDownloadFinished;

// 다운로드할 파일의 목록을 요청합니다.
// 파라미터로 서버 주소(포트를 포함한 URL)와 파일이 저장될 경로를 넘겨줍니다.
downloader.GetDownloadList("http://127.0.0.1:8020", "target/path", "file/path");

콜백 함수 중 ReadyCallbackFinishedCallback 함수는 꼭 필요한 콜백이고 나머지는 필요에따라 등록해서 사용하면 됩니다.

다운로드의 시작은 파일 목록을 받는 것부터 시작합니다. GetDownloadList 함수를 호출해서 리소스 목록을 받고 로컬 파일들을 확인합니다. GetDownloadList 함수의 파라미터에는 서버 URL과 파일이 저장될 폴더의 경로를 지정합니다. file/path 는 리스트 파일명이 포함된 경로이며, 첫 번째 파라미터인 서버 URL 주소를 제외한 뒷 부분 경로만 전달하면 됩니다. 게임서버로 요청할 경우엔 이 값을 지정하지 않아도 괜찮지만 CDN으로 직접 리소스 목록 파일을 요청할 경우에는 이 값을 반드시 입력해 주세요.

VerifyCallback 파일마다 유효성 검사가 끝났을 때 호출됩니다.
ReadyCallback 리소스 목록을 받고 파일 유효성 검사를 마친 후 다운로드 준비가 완료됐을 때 호출됩니다.
UpdateCallback 다운로드 중인 파일의 진행상황을 알려줍니다. UI 업데이트용으로 사용하시면 좋습니다.
FinishedCallback 리소스 다운로드가 완료되면 호출됩니다.

GetDownloadList 함수를 호출한 후 다운로드 준비가 완료되면 ReadyCallback 함수가 호출됩니다. ReadyCallback 함수가 호출된 후 StartDownload 함수를 호출해서 다운로드를 시작할 수 있습니다. 다운로드가 완료되면 FinishedCallback 함수가 호출됩니다.

downloader.StartDownload();

새로 다운 받을 파일이 없다면 ReadyCallback 함수는 호출되지 않고 FinishedCallback 함수만 호출됩니다.

FunapiHttpDownloader 클래스에는 다운로드를 시작하기 전에 몇 가지 확인할 수 있는 정보들이 있습니다.

TotalDownloadFileCount 다운로드 받을 전체 파일 개수
TotalDownloadFileSize 다운로드 받을 전체 파일의 크기 (bytes)
CurrentDownloadFileCount 다운로드가 완료된 파일 개수
CurDownloadFileSize 다운로드가 완료된 파일의 크기 (bytes)

이 Property들은 ReadyCallback 함수가 호출된 이후에 사용해야 합니다.

37.12. 소셜네트워크 플러그인

Funapi Social plugin을 사용하면 Facebook과 Twitter의 계정으로 로그인 및 포스팅 등을 손쉽게 할 수 있습니다. 설치 패키지 파일은 플러그인의 루트 폴더에 additional-plugins/Packages 폴더 안에 있습니다. Scripts 폴더는 패키지를 Import 했을 때 더해지는 파일들을 풀어놓은 폴더입니다. 패키지로 설치했다면 Scripts 폴더의 파일을 복사하지 않아도 됩니다. 샘플 소스는 Tester 폴더에 설치됩니다.

Tip

소셜네트워크 플러그인은 현재는 유니티 버전만 제공됩니다.

37.12.1. 인터페이스 클래스

Funapi Social Plugin 에는 공통으로 사용하는 인터페이스 클래스가 있습니다. SocialNetwork 라는 클래스인데 아래와 같은 인터페이스를 갖고 있습니다.

public abstract class SocialNetwork : MonoBehaviour
{
  // 친구 정보를 담고 있는 클래스입니다.
  public class UserInfo
  {
      public string id;         // 계정 아이디
      public string name;       // 계정 이름
      public string url;        // 프로필 사진 url
      public Texture2D picture; // 프로필 사진 이미지
  }

  // 초기화 함수입니다.
  // 페이스북의 경우 파라미터가 필요 없지만 트위터는 consumer key 와 consumer secret 값을 전달해야 합니다.
  public abstract void Init (params object[] param);

  // 메시지를 포스팅하는 함수입니다.
  public virtual void Post (string message)

  // 메시지와 이미지를 함께 포스팅하는 함수입니다.
  public virtual void PostWithImage (string message, byte[] image)

  // 메시지와 함수가 호출되는 시점의 화면을 함께 포스팅하는 함수입니다.
  public virtual void PostWithScreenshot (string message)

  // 친구 목록에서 해당하는 아이디의 친구 정보를 반환합니다.
  public UserInfo FindFriendInfo (string id)

  // 친구 목록에서 해당하는 인덱스의 친구 정보를 반환합니다. (인덱스는 0부터 시작)
  public UserInfo FindFriendInfo (int index)

  // 초대 목록에서 해당하는 아이디의 친구 정보를 반환합니다.
  public UserInfo FindInviteFriendInfo (string id)

  // 초대 목록에서 해당하는 인덱스의 친구 정보를 반환합니다. (인덱스는 0부터 시작)
  public UserInfo FindInviteFriendInfo (int index)

  // 내 계정의 아이디를 반환합니다.
  public string my_id;

  // 내 계정의 이름을 반환합니다.
  public string my_name;

  // 내 계정의 프로필 사진 Texture2D를 반환합니다.
  public Texture2D my_picture;

  // 친구 목록의 전체 개수를 반환합니다.
  public int friend_list_count;

  // 초대 목록의 전체 개수를 반환합니다.
  public int invite_list_count;

  // 이벤트 함수 원형
  public delegate void EventHandler (SNResultCode code);
  public delegate void PictureDownloaded (UserInfo user);

  // 초기화, 로그인, 친구 목록 가져오기, 포스팅 등의 응답에 대한 이벤트 함수입니다.
  // 콜백 호출시 파라미터로 enum SNResultCode 값이 전달됩니다.
  // 요청에 실패할 경우 SNResultCode.kError가 전달됩니다.
  public event EventHandler OnEventCallback;

  // 프로필 사진을 받으면 호출되는 이벤트 함수입니다.
  // 해당 프로필 사진의 UserInfo가 호출과 함께 전달됩니다.
  public event PictureDownloaded OnPictureDownloaded;
}

이 SocialNetwork 클래스를 상속받아 만들어진 FacebookConnector 클래스와 TwitterConnector 클래스를 사용하면 됩니다.

Note

플러그인 연동을 시작하기 전에 해당 소셜 앱을 만들어야 합니다. 페이스북은 페이스북 앱, 트위터는 트위터 앱 을 만들어 주세요. 페이스북의 경우에는 저희가 제공하는 facebook-plugin 을 설치하기 전에 Facebook SDK for Unity 도 설치해야 합니다.

37.12.2. 페이스북

플러그인 기능

Facebook SDK 의 기능을 Wrapping 해서 몇 가지 기능만을 제한적으로 제공하고 있습니다. 아래에 제공하는 기능 외에 사용하고 싶은 기능이 있다면 Funapi support 로 요청하실 수 있습니다.

  • 페이스북 계정으로 로그인
  • 내 정보와 프로필 사진 요청
  • 친구 목록 및 프로필 사진 요청
  • 게임에 초대 가능한 친구 목록 요청
  • 내 담벼락에 포스팅

FacebookConnector 는 MonoBehaviour 객체이므로 Scene 에 미리 등록되어 있어야 합니다. 다음은 등록된 Object 에서 FacebookConnector 객체를 가져와서 초기화하는 코드입니다.

// FacebookConnector 컴포넌트를 가져옵니다.
FacebookConnector facebook = GameObject.Find("object name").GetComponent<FacebookConnector>();

// 초기화, 로그인, 포스팅 등의 요청에 대한 응답을 알려주는 이벤트 핸들러를 등록합니다.
facebook.OnEventCallback += OnEventHandler;

// 프로필 사진을 다운받으면 호출되는 이벤트 핸들러를 등록합니다.
facebook.OnPictureDownloaded += delegate(SocialNetwork.UserInfo user) {
    // do something with profile picture
};

// 초기화 함수를 호출합니다.
facebook.Init();

페이스북 로그인

로그인 함수는 두 종류가 있는데 읽기 권한만 필요할 때는 LogInWithRead 함수로 로그인하고 쓰기 권한까지 필요할 때는 LogInWithPublish 함수로 로그인해야 합니다.

로그인 함수의 파라미터로는 권한 목록을 전달해야 합니다. 이 권한은 페이스북 앱의 Status & Review 페이지에서 확인할 수 있는데 기본적으로 제공하는 기능이 몇 가지 있고 포스팅 등의 대부분의 기능은 권한 요청을 하고 페이스북에서 승인을 해준 뒤에 앱에서 사용이 가능합니다. 유니티 에디터에서 테스트할 때는 Graph API Explorer 를 통해 테스트용 Access Token 을 얻을 수 있습니다.

// 담벼락에 글을 쓰기 위해 publish 권한을 사용할 수 있는 LogInWithPublish 함수로 로그인합니다.
facebook.LogInWithPublish(new List<string>() {
    "public_profile", "email", "user_friends", "publish_actions"});

// 친구 목록을 요청합니다. 최대 100명의 친구 목록을 가져옵니다.
facebook.RequestFriendList(100);

// 게임에 초대 가능한 친구 목록을 요청합니다. 최대 100명의 초대 목록을 가져옵니다.
facebook.RequestInviteList(100);

// 내 담벼락에 현재 화면과 함께 메시지를 포스팅합니다.
facebook.PostWithScreenshot("plugin test post.");

친구 목록을 요청할 때 자동으로 사진도 함께 요청하는데 모든 친구의 사진이 당장 필요하지 않다면 선택적으로 사진을 다운받을 수도 있습니다.

// 이 옵션을 false로 주면 친구 목록을 요청할 때 프로필 사진을 같이 요청하지 않습니다.
// 이 값은 친구 목록을 요청하기 전에 설정해야 합니다. FacebookConnector 객체를 초기화 할 때 설정하는 것이 좋습니다.
facebook.auto_request_picture = false;

// 친구 목록에 있는 유저들의 프로필 사진을 요청합니다.
// 시작 인덱스 값과 최대 개수를 전달합니다.
facebook.RequestFriendPictures(0, 20);

// 초대 목록에 있는 유저들의 프로필 사진을 요청합니다.
// 시작 인덱스 값과 최대 개수를 전달합니다.
facebook.RequestInvitePictures(0, 20);

사진을 요청하면 순차적 비동기 방식으로 사진을 다운로드합니다. 프로필 사진의 다운로드가 완료되면 PictureCallback 이벤트 함수가 호출됩니다. 사진의 다운로드가 완료됐을 때 알림을 받고 싶다면 FacebookConnector 객체를 초기화 할 때 PictureCallback 이벤트에 콜백 함수를 등록해야합니다.

37.12.3. 트위터

플러그인 기능

트위터 플러그인에서 제공하는 기능은 아래와 같습니다. 아래 기능 외에 사용하고 싶은 기능이 있다면 Funapi support 로 요청하실 수 있습니다.

  • 트위터 계정으로 로그인
  • 내 정보와 프로필 사진 요청
  • 친구 목록 및 프로필 사진 요청
  • 내 트위터에 글 올리기

TwitterConnector 는 MonoBehaviour 객체이므로 Scene 에 미리 등록되어 있어야 합니다. 다음은 등록된 Object 에서 TwitterConnector 객체를 가져와서 초기화하는 코드입니다.

// TwitterConnector 컴포넌트를 가져옵니다.
TwitterConnector twitter = GameObject.Find("object name").GetComponent<TwitterConnector>();

// 초기화, 로그인, 포스팅 등의 요청에 대한 응답을 알려주는 이벤트 핸들러를 등록합니다.
twitter.OnEventCallback += OnEventHandler;

// 프로필 사진을 다운받으면 호출되는 이벤트 핸들러를 등록합니다.
twitter.OnPictureDownloaded += delegate(SocialNetwork.UserInfo user) {
    // do something with profile picture
};

// 초기화 함수를 호출합니다.
// 트위터 앱의 Consumer Key와 Consumer Secret 값을 전달합니다.
twitter.Init("4RnU4YDXmu8vmwKW5Lgpej3Xc",
             "voDDAoaTNXj8VjuWRDhfrnCpa9pnVgpRhBJuKwjJpkg62dtEhd");

트위터 초기화는 Consumer KeyConsumer Secret 값을 전달인자로 넣어서 Init() 함수를 호출해야 합니다. 이 값은 트위터 앱 의 [Keys and Access Tokens] 탭에서 확인할 수 있습니다.

트위터 로그인

트위터는 로그인하는 방법이 상대적으로 복잡하지만 한번 로그인하고 앱을 승인한 뒤에는 처음 로그인할 때 받은 Access Token 을 계속 사용하게 되므로 이와 같은 복잡한 과정은 한번만 거치면 됩니다.

// 로그인 함수를 호출해 앱을 승인하고 PIN Code를 얻습니다.
// 웹을 통해서 얻은 PIN Code를 입력받는 UI는 클라이언트에서 구현해야 합니다.
twitter.Login();

// Access Token을 요청합니다.
// 로그인 함수를 통해 얻은 PIN Code를 파라미터로 전달합니다.
twitter.RequestAccess("pin code");

RequestAccess() 함수를 호출해 얻은 Access Token 은 영구적으로 사용 가능한 인증 값으로 이 값만 있으면 언제든지 트위터에 포스팅을 할 수 있습니다. 한 번 로그인을 한 뒤에는 이 값을 플러그인에서 암호화해서 저장해두었다가 다음 Init() 함수 호출시에 확인하고 인증과정 없이 콜백함수를 통해 로그인이 완료되었음을 알려줍니다.

로그인이 되었다면 메시지를 포스팅 할 수 있습니다. 아래와 같이 메시지를 보내면 내 트위터에 글을 올려줍니다.

twitter.Post("Funapi plugin test message~");

37.13. Unity 플러그인

Unity client plugin은 GitHub 를 통해 받으실 수 있습니다. 암호화가 포함된 버전이 필요하시면 Funapi support 로 요청 하셔야합니다.

GitHub 에서 받은 소스 코드에서 Assets 폴더의 파일들 중 Funapi, Plugins 폴더를 플러그인을 사용할 프로젝트의 Assets 폴더로 복사하면 됩니다. HTTPS 를 사용한다면 Editor와 Resources 폴더도 함께 복사해 주세요. Tester 폴더에는 플러그인의 예제 파일들이 있습니다.

37.13.1. Unity에서 protobuf 사용하기

iOS / Android의 제약사항 때문에 protobuf-net 으로 생성한 C# 코드를 바로 사용할 수는 없습니다. protobuf.NET 이 생성하는 C# 파일을 바로 사용하면 메시지를 읽는 some_protobuf_message.GetExtension(...) 형태의 코드에서, 아래와 유사한 오류 메시지를 남기면서 libmono 의 JIT 컴파일러 안에서 크래시 합니다.

F/mono  (1021): * Assertion at mini-arm.c:2595, condition `pdata.found == 1' not met
F/libc  (1021): Fatal signal 11 (SIGSEGV) at 0x0000600d (code=-6), thread 1033 (UnityMain)

따라서 메시지 serialize/deserialize 하는 부분을 C# 코드가 아닌 AOT 빌드한 .dll 을 사용하도록 변경해야 합니다.

Protobuf 용 .dll 빌드하게 설정하기

게임 서버의 가장 상위 디렉터리에 있는 CMakeLists.txt 를 보면 다음과 같이 GENERATE_UNITY_PROTOBUF_DLL 라는 설정이 있습니다. 이 값을 true 로 변경하면 빌드 디렉터리에 필요한 .dll 파일이 생성합니다.

# Source managed by git can append a build number from git commit id.
set(PACKAGE_WITH_BUILD_NUMBER_FROM_GIT false)

# Source managed by svn can append a build number from svn info.
set(PACKAGE_WITH_BUILD_NUMBER_FROM_SVN false)

# Generate .DLL for Unity Engine
set(GENERATE_UNITY_PROTOBUF_DLL true)

생성한 .dll 파일 복사하기

게임 서버 빌드 디렉터리, 예를 들어 hello 란 프로젝트의 디버그 설정을 빌드한 경우에는 hello-build/debug/unity_dll 디렉터리에 다음 .dll 파일이 생성됩니다.

  • protobuf-net.dll : Unity 엔진 용의 protobuf-net 파일
  • messages.dll: 엔진에서 생성한 protobuf 메시지 정의
  • FunMessageSerializer.dll: 엔진에서 생성한 protobuf 읽고/쓰는 루틴

이 파일을 복사해서 클라이언트의 Assets 디렉터리에 복사하고, Unity 에디터에서 확인하시면 됩니다. Windows 환경이라면 https://winscp.net/eng 에서 제공하는 프로그램을 사용하면 편리합니다.

Tip

개별 메시지와 메시지들의 enum 에 대한 소스 코드는 서버 빌드 디렉터리 밑의 unity_cs 디렉터리에서 messages.cs, messages_enum.cs 파일로 생성됩니다.

37.13.2. C# Runtime Test Code

플러그인 폴더 내에 플러그인 봇 테스트를 위한 C# Runtime 테스트 코드가 있습니다. 유니티에서는 동시에 열 수 있는 소켓의 최대 수에 제한이 있기 때문에, 테스트를 위한 봇 프로그램을 직접 작성하는 것을 추천합니다. 좀 더 자세한 설명은 방법2: 클라이언트 플러그인을 이용 문서에서 확인할 수 있습니다.

37.13.3. Unity Plugin 로그 보기

플러그인 관련 로그는 유니티 에디터나 C# Runtime 실행시에만 표시됩니다. 로그를 남기는 것 자체가 게임의 퍼포먼스에 영향을 줄 수도 있기 때문에 에디터로 실행할 때 이외에는 로그를 남기지 않도록 하고 있습니다. 테스트를 위해 플러그인 로그를 항상 보고 싶다면 Funapi/DebugUtils.cs 파일 상단의 ENABLE_LOG 가 항상 정의되도록 수정하시면 됩니다. 기본 로그 이외에 좀 더 자세한 로그가 보고 싶을 경우에는 ENABLE_DEBUG 를 활성화시켜 주세요.

//#define ENABLE_DEBUG    // 디버그 로그 표시

// 유니티 에디터 or C# Runtime일 경우 로그 표시
#if ENABLE_DEBUG || UNITY_EDITOR || NO_UNITY
#define ENABLE_LOG
#endif

ENABLE_DEBUG define 은 Funapi/DebugUtils.cs 파일에서 주석을 풀어도 되고 유니티 빌드세팅에서 define symbol 을 추가해도 됩니다. 빌드세팅의 Other Settings 탭에서 Scripting Define Symbols 항목에 ENABLE_DEBUG 를 추가하면 됩니다.

37.13.4. 로그 저장하기

플러그인의 로그를 문자열이나 파일로 저장할 수 있습니다. 이 기능을 사용하려면 Funapi/DebugUtils.cs 파일에서 ENABLE_SAVE_LOG define 을 활성화하면 됩니다.

아래는 FunDebug 클래스에 있는 로그 저장과 관련된 함수와 프로퍼티들입니다. 이와 관련된 샘플코드는 Tester/Tester.cs 파일에 있습니다.

// 이 옵션을 활성화하면 로그가 버퍼에 저장됩니다.
#define ENABLE_SAVE_LOG

public class FunDebug
{
  ...

  // 버퍼에 저장된 내용을 파일로 저장합니다. 버퍼의 크기는 1MBytes 입니다.
  // 버퍼가 꽉 차면 자동으로 파일로 저장되고 버퍼가 초기화됩니다.
  // 파일은 로컬 저장 경로의 'Data/Logs/' 폴더에 저장됩니다.
  public static void SaveLogs();

  // 저장된 로그의 크기를 반환합니다.
  public static int GetLogLength();

  // 저장된 전체 로그를 반환합니다.
  public static string GetLogString();

  // 버퍼를 초기화합니다.
  public static void ClearLogBuffer();

  // 저장된 모든 로그 파일을 삭제합니다.
  public static void RemoveAllLogFiles();
}

37.13.5. 디버그 로그 파일 만들기

유니티 플러그인을 사용해서 개발 도중 플러그인에 문제가 있다고 판단될 경우 아래와 같은 방법으로 로그를 저장해서 파일과 함께 재현 스텝을 알려주시면 감사하겠습니다.

Funapi/DebugUtils.cs 파일에서 아래 define 들을 활성화합니다.

#define ENABLE_DEBUG
#define ENABLE_SAVE_LOG

게임 종료 시점이나 재현 상황이 발생한 직후 아래 함수를 호출해서 로그를 저장합니다.

// 로그를 파일로 저장
FunDebug.SaveLogs();

에디터에서 실행할 경우 이렇게 저장된 로그는 프로젝트 폴더의 Assets 이 있는 폴더와 같은 경로에 Data/Logs 폴더에서 확인할 수 있습니다. 모바일 기기에서 테스트할 경우에는 디바이스에서 앱의 저장 폴더에 접근하는 것을 제한하고 있으므로 파일에 접근이 어려울 수 있습니다.

37.14. Unreal Engine 4 플러그인

Unreal Engine 4 plugin은 GitHub 를 통해 받으실 수 있습니다. 암호화가 포함된 버전이 필요하시면 Funapi support 로 요청 메일을 보내주십시오.

37.14.1. Editor에서 실행하기

플러그인에는 FunapiSession 테스트를 위한 간단한 샘플코드가 포함되어 있습니다. 이 액터 클래스의 객체가 언리얼 에디터에 표시가 되어야 tester 샘플맵이 오류없이 플레이가 됩니다. 처음 프로젝트를 실행하면 에디터에 funapi_tester 객체가 표시되지 않을 수 있습니다. 이 때 프로젝트를 한 번 컴파일하면 클래스 객체가 만들어집니다. 혹시 컴파일을 해도 에디터에 표시되지 않는다면, 에디터를 종료 후 프로젝트를 다시 로드해야합니다. funapi_tester 객체가 에디터의 콘텐츠 브라우저(C++ Class > funapi_plugin_ue4)에 보인다면 tester 맵을 로드한 후 플레이 버튼을 눌러 샘플 프로그램을 실행해 볼 수 있습니다.

37.14.2. 외부 라이브러리 컴파일하기

언리얼 플러그인에서는 다음과 같은 외부 라이브러리를 사용하고 있습니다.

libcurl HTTP 통신에 필요한 기능을 사용하고 있습니다.
libcrypto Openssl 라이브러리에 포함된 암호화 라이브러리. MD5를 사용하고 있습니다.
libprotobuf Google protocol buffer 라이브러리입니다.
libsodium ChaCha20 AES-128 암호화를 위한 라이브러리입니다.

위의 라이브러리를 위한 컴파일용 스크립트와 컴파일된 라이브러리 파일을 함께 배포하고 있습니다. 플러그인 폴더에 ThirdParty 폴더 안에 빌드 스크립트와 라이브러리 파일들이 플랫폼 별로 구분되어 있습니다.

ThirdParty
  ㄴ build     // 라이브러리 빌드 스크립트 파일들
  ㄴ include   // 헤더 파일들
  ㄴ lib       // 라이브러리 파일들
  ㄴ proto     // .proto 파일과 proto 파일용 컴파일 스크립트

배포된 컴파일용 스크립트로 라이브러리를 직접 빌드해서 사용해도 되지만 특별히 라이브러리를 새로 빌드해야 할 이유가 있는 것이 아니라면 배포된 라이브러리 파일을 그대로 사용할 것을 권장합니다.

37.14.3. Protobuf 파일 빌드하기

.proto 파일을 빌드하려면 ThirdParty/proto/make-win.bat 파일이나 make-mac.sh 파일을 실행하면 됩니다. .proto 파일을 빌드 목록에 추가하려면 배치 파일을 열어서 하단 파일 목록에 추가해주면 됩니다. 배치 파일을 실행하면 결과 파일이 Source 폴더 안에 funapi 폴더에 추가/수정됩니다.

37.15. Cocos2d-x 플러그인

Cocos2d-x client plugin은 GitHub 를 통해 받으실 수 있습니다. 암호화가 포함된 버전이 필요하시면 Funapi support 로 요청 메일을 보내주십시오.

37.16. 버그 신고

Client plugin 에 대한 건의사항이나 버그 신고는 Funapi support 로 메일을 보내주십시오.