27. 内容支持Part 3: 组播、聊天

组播和聊天是向连到频道上的所有用户传输消息的功能。 此时,当用户属于同一频道时,即使属于不同服务器,也能收到消息。

组播和聊天的差异如下。

  • 组播对传输的消息没有限制。 即可自由发送 JSONProtobuf ,相关消息将原封不动地传输给已连接在频道上的所有用户。

    Tip

    由于它具有随意发送消息,接受共享的特性,所以组播功能本身可作为中继服务器使用。

  • 聊天是专门负责玩家之间收发聊天消息的功能。 因此只能输入字符串,且仅能传输相应的字符串。 聊天作为组播的一种特殊情况,使用组播功能进行处理。

27.1. 使用组播功能时所需要的设置

27.1.1. 设置MANIFEST

按如下所示,设置MANIFEST.json文件。

Important

为了使用组播和聊天功能,RpcService须要被激活。与 RpcService 的设置有关的具体说明,请参考 分布式处理功能的设置参数

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

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

        ....,

        "RpcService": {
          "rpc_enabled": true,
          ...
        }
    }
}
  • max_member_count_per_channel: 指定每个频道的玩家定员数量。如为0,则为无限制。如非0,则可从2个以上开始指定。(type=uint64, default=0)

    Note

    通道入场定员数无法设置为1人。

27.1.2. 服务器代码

为了使用组播和聊天功能,须要在服务器中做的工作只有 上面提到的设置MANIFEST。如使用客户端插件中的组播和 聊天功能,插件和iFun引擎将自动处理。

但是若想要对组播或聊天消息进行hooking时,可使用 멀티캐스팅 메시지 전송 후킹 中所介绍的方法。

27.1.3. 客户端代码

客户端的组播和聊天功能已经在客户端插件中实现。 具体内容请参考客户端插件文档的 组播和聊天

Important

无法在客户端同时使用组播和聊天。须要仅使用组播(FunapiMulticastClient)或仅使用聊天(FunapiChatClient)。

Tip

在通过Unity3d写好的 GitHub engine-plugin-unity3d 中也可以查看组播和聊天功能的使用示例源代码。请参考 GitHub FunapiMulticastClient

27.2. 멀티캐스팅 메시지 전송 후킹

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

27.2.1. 메시지 확인하기

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

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

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);
}

27.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);
}

27.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);
}

27.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);
}

27.2.3. 예제: 채널 내 메시지를 로그로 남기기

 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);
}