28. Contents support part 3: Multicasting and chatting

Multicasting and chatting are ways to send messages to all users connected to a channel. All messages can be received using these methods, even if users in the same channel are on different servers.

The difference between multicasting and chatting is the following:

  • Multicasting doesn’t limit messages that can be sent. You can send messages as JSON or Protobuf, and a message will be sent to all users in a channel as is.

    Tip

    Since messages can be sent and shared at will, the multicasting feature itself can be used as a relay server.

  • Chatting refers specifically to chat messages sent between users. Only text strings can be entered and sent. In special instances of multicasting, chatting can be handled using the multicasting function.

28.1. Settings to use multicasting

28.1.1. MANIFEST settings

Set up the MANIFEST.json file as follows:

Important

To use multicasting and chatting, you need to activate RpcService. For more about RpcService, please refer to Distribution parameters.

{
    "dependency": {
        .....,

        "MulticastServer": {
          "transport_for_multicast_channel": "tcp",
          "max_member_count_per_channel": 0
        },

        ....,

        "RpcService": {
          "rpc_enabled": true,
          ...
        }
    }
}
  • transport_for_multicast_channel: 멀티캐스트 시 사용할 Transport(연결)을 지정 합니다. tcp와 websocket 중 선택할 수 있습니다. (type=string, default=”tcp”)

  • max_member_count_per_channel: Sets the maximum number of users per channel. Infinite if 0. If not 0, you can set this to 2 or more. (type=uint64, default=0)

    Note

    You cannot set the maximum number of users to 1.

28.1.2. Server code

All you need to do on the server to use multicasting and chatting is to configure the MANIFEST. When you use the multicasting and chatting functions implemented in client plugins, iFun Engine recognizes and handles them.

However, if you want to hook multicasting or chatting messages, you can use the methods mentioned in Multicasting message transmission hooking.

28.1.3. Client code

Client multicasting and chatting is already implemented in the client plugin. For more, please refer to client plugin documentation at Multicasting and chatting.

Important

You cannot simultaneously use multicasting and chatting on a client. You must only use multicasting (FunapiMulticastClient) or chatting (FunapiChatClient).

Tip

You can see more examples of multicasting and chatting use at GitHub engine-plugin-unity3d created with Unity3D. Please see GitHub FunapiMulticastClient.

28.2. Multicasting message transmission hooking

아이펀 엔진에서는 전송되는 멀티캐스팅 메시지를 후킹할 수 있는 기능을 제공하고 있습니다. 메시지를 수정하거나 전송 여부를 선택할 수 있으며, 전송 완료된 메시지를 전달받을 수도 있습니다.

28.2.1. Hook registration function

채널 안에서 전송되는 메시지를 확인할 때 사용합니다. 메시지를 수정하거나 전송 여부를 선택할 수 있습니다. 인자로 넘어오는 세션에게 별도의 게임 메시지를 보낼 수도 있습니다.

You can use MulticastServer::InstallJsonMessageChecker() and MulticastServer::InstallProtobufMessageChecker() to hook messages transmitted within a channel.

class MulticastServer {
 public:
  typedef function<
      bool(const string & /*channel*/,
           const string & /*sender*/,
           const Ptr<Session> & /*session*/,
           Json * /*message*/)> JsonMessageChecker;
  typedef function<
      bool(const string & /*channel*/,
           const string & /*sender*/,
           const Ptr<Session> & /*session*/,
           const Ptr<FunMessage> & /*message*/)> ProtobufMessageChecker;

  static void InstallJsonMessageChecker(
      const JsonMessageChecker &json_message_checker);

  static void InstallProtobufMessageChecker(
      const ProtobufMessageChecker &protobuf_message_checker);
};
public static class MulticastServer
{
  public delegate bool JsonMessageChecker(
    string channel, string sender, Session session, ref JObject message);

  public delegate bool ProtobufMessageChecker(
    string channel, string sender, Session session, ref FunMessage message);

  public static void InstallJsonMessageChecker (JsonMessageChecker chekcer);

  public static void InstallProtobufMessageChecker (ProtobufMessageChecker chekcer);
}

28.2.1.1. 예제 - 메시지 변경하기

아래 예제에서는 JSON 을 이용한 채팅 메시지의 내용을 변경해보겠습니다. 전달되는 JSON 메시지는 다음과 같다고 가정하겠습니다.

{
  "message": "Hello World"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
bool CheckJsonMessage(const string &channel, const string &sender,
                      const Ptr<Session> &session, Json *message) {
  LOG_ASSERT(message != NULL);
  LOG_ASSERT(message->HasAttribute("message", Json::kString));

  // 메시지를 변경합니다.
  (*message)["message"] = "Hello iFun";

  // true 를 반환하여 변경된 message 를 전송합니다.
  return true;
}

void Install() {
  MulticastServer::InstallJsonMessageChecker(CheckJsonMessage);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static bool CheckJsonMessage(
  string channel, string sender, Session session, ref JObject message)
{
  if (message["message"] != null) {
    // 메시지를 변경합니다.
    message["message"] = "Hello iFun";
  }

  return true;
}

public static bool Install(ArgumentMap arguments)
{
  MulticastServer.InstallJsonMessageChecker(CheckJsonMessage);
}

28.2.1.2. 예제 - 메시지 전송 실패 처리하기

다음은 1초안에 2회 이상 채팅 메시지를 전송할 경우 실패 처리하는 예제입니다. 실패하면 예제에서는 sc_chat_error 라는 가상의 메시지 타입으로 메시지를 보냅니다. 엔진에서는 메시지 타입명과 내용을 제한하지 않기 때문에 게임에 맞게 메시지를 만들어서 보낼 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool CheckJsonMessage(const string &channel, const string &sender,
                      const Ptr<Session> &session, Json *message) {
  LOG_ASSERT(message != NULL);
  LOG_ASSERT(message->HasAttribute("message", Json::kString));

  // 마지막 채팅 시간을 검사합니다.
  int64_t last_chat_time = 0;
  if (session->GetFromContext("last_chat_time", &last_chat_time)) {
    int64_t elapsed_time = WallClock::GetTimestampInSec() - last_chat_time;
    if (elapsed_time < 1) {
      // 1초안에 2회 이상 채팅은 금지되어 있습니다.
      Json response;
      response["result"] = false;
      response["message"] = "your error message.";
      session->SendMessage("sc_chat_error", response, kDefaultEncryption, kTcp);
      return false;
    }
  }

  // 만약 다른 조건이 있다면 여기서 처리합니다.

  last_chat_time = WallClock::GetTimestampInSec();
  session->AddToContext("last_chat_time", last_chat_time);

  (*message)["message"] = "Hello iFun";
  return true;
}

void Install() {
  MulticastServer::InstallJsonMessageChecker(CheckJsonMessage);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static bool JsonChecker(
    string channel, string sender, Session session, ref JObject message)
{
    long last_chat_time = 0;

    if (session.GetFromContext("last_chat_time", out last_chat_time))
    {
      long elapsed_time = WallClock.GetTimestampInSec() - last_chat_time;
      if (elapsed_time < 1)
      {
        // 1초안에 2회 이상 채팅은 금지되어 있습니다.
        JObject response = new JObject();
        response["result"] = false;
        response["message"] = "your error message.";
        session.SendMessage("sc_chat_error", response,
            Session.Encryption.kDefault, Session.Transport.kTcp);

        return false;
      }
    }

    last_chat_time = WallClock.GetTimestampInSec();
    session.AddToContext("last_chat_time", last_chat_time);

    if (message["message"] != null) {
      // 메시지를 변경합니다.
      message["message"] = "Hello iFun";
    }

    return true;
}

public static bool Install(ArgumentMap arguments)
{
    MulticastServer.InstallJsonMessageChecker(CheckJsonMessage);
}

28.2.2. 메시지 전달받기

전송 완료된 메시지를 전달받고 싶을 때 사용합니다. 전송 완료된 메시지를 로그로 기록하고 싶다면 이 기능을 사용하면 됩니다.

멀티캐스팅 메시지로 JSON 을 이용할 경우에는 MulticastServer::InstallJsonMessageHook() 함수를 사용합니다. Protobuf 인 경우에는 MulticastServer::InstallProtobufMessageHook() 함수를 사용합니다.

class MulticastServer {
 public:
  typedef function<
      void(const string & /*channel*/,
           const string & /*sender*/,
           const SessionId & /*session_id*/,
           const Json & /*message*/)> JsonMessageHook;
  typedef function<
      void(const string & /*channel*/,
           const string & /*sender*/,
           const SessionId & /*session_id*/,
           const Ptr<const FunMessage> & /*message*/)> ProtobufMessageHook;

  static void InstallJsonMessageHook(const JsonMessageHook &hook);
  static void InstallProtobufMessageHook(const ProtobufMessageHook &hook);
};
public static class MulticastServer
{
  public delegate void JsonMessageHook (string channel, string sender,
                                        Guid session_id, JObject message);
  public delegate void ProtobufMessageHook (string channel, string sender,
                                            Guid session_id, FunMessage message);

  public static void InstallJsonMessageHook (JsonMessageHook hook);
  public static void InstallProtobufMessageHook (ProtobufMessageHook hook);
}

28.2.3. E.g. Saving messages inside a channel to a log

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void OnJsonHook(const string &channel,
                const string &sender,
                const SessionId &session_id,
                const Json &message) {
  // message 에는 hello 가 있다고 가정하겠습니다.
  const string msg = message["hello"].GetString();

  LOG(INFO) << "OnJsonHook: "
            << "channel=" << channel
            << ", sender=" << sender
            << ", session_id=" << session_id
            << ", message=" << msg
            << ", json_message=" << message.ToString();
}


void OnProtobufHook(const string &channel,
                    const string &sender,
                    const SessionId &session_id,
                    const Ptr<const FunMessage> &message) {
  // FunMulticastMessage 를 확장하여 만든 PbufHelloMessage 를
  // 전송했다고 가정하겠습니다.
  const FunMulticastMessage &multicast_msg = message->GetExtension(multicast);
  const PbufHelloMessage &pbuf_hello_msg = multicast_msg.GetExtension(pbuf_hello);
  const string &msg = pbuf_hello_msg.message();

  LOG(INFO) << "OnProtobufHook: "
            << "channel=" << channel
            << ", sender=" << sender
            << ", session_id=" << session_id
            << ", message=" << msg
            << ", pbuf_message=" << message->ShortDebugString();
}

void Install() {
  MulticastServer::InstallJsonMessageHook(OnJsonHook);
  MulticastServer::InstallProtobufMessageHook(OnProtobufHook);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
static string kJsonHookMsgFormat = String.Join(
    "", new string[]{
        "OnJsonHook: channel= {0},",
        "sender= {1},",
        "session_id= {2},",
        "message={3},",
        "json_message = {4}"});

static string kPbufHookMsgFormat = String.Join(
    "", new string[]{
        "OnProtobufHook: channel= {0},",
        "sender= {1},",
        "session_id= {2},",
        "message={3},",
        "json_message = {4}"});

public static void OnJsonHook(string channel, string sender,
                              Guid session_id, JObject message)
{
  // message 에는 hello 가 있다고 가정하겠습니다.
  string msg = (string) message ["hello"];

  Log.Info (kJsonHookMsgFormat, channel, sender,
            session_id.ToString(), msg, message.ToString());
}

public static void OnProtobufHook(string channel, string sender,
                                  Guid session_id, FunMessage message)
{
  FunMulticastMessage multicast_msg;
  if (!message.TryGetExtension_multicast (out multicast_msg)) {
    return;
  }

  // FunMulticastMessage 를 확장하여 만든 PbufHelloMessage 를
  // 전송했다고 가정하겠습니다.
  PbufHelloMessage hello_msg;
  if (!multicast_msg.TryGetExtension_pbuf_hello (out hello_msg)){
    return;
  }

  Log.Info (kPbufHookMsgFormat, channel, sender, session_id.ToString (),
            hello_msg, message.ToString ());
}

public static void Install(ArgumentMap arguments)
{
  MulticastServer.InstallJsonMessageHook (OnJsonHook);
  MulticastServer.InstallProtobufMessageHook (OnProtobufHook);
}