39. Client programming part 1: Plugins¶
You can use client plugins for network communication, to check announcements, to download resources, etc. Client plugins that can be used in Unity, Unreal Engine 4, and Cocos2d-x are currently supported. You can download client plugins from GitHub.
This documentation will explain how to use client plugins as well as provide and explain tips and warnings. The explanation below was written based on a Unity plugin.
39.1. Summary¶
39.1.1. What is a session?¶
Server-client network connections are managed through sessions. Sessions can be directly closed or maintained as long as there is no timeout if the network communication lasts longer than a set time. That is, a session can be maintained on the server even if the network connection is closed, and if there is a session with the same ID upon reconnection, messages continue to be transmitted as if there had been no disconnection.
One session can have up to 3 transport connections for each of TCP, UDP, and HTTP. If you want several connections with the same protocol, you also need to create several sessions.
Tip
Details about internal session implementation are explained in (Advanced) iFun Engine Network Stack.
39.1.1.1. Session Id¶
When the server is contacted, a session ID is issued by the first server, and all messages transmitted to and from the server later include this session ID.
If you use JSON, the session ID is a text string of 36 bytes. You can use 16-byte arrays if using Protobuf, or you can use 36-byte text strings as with JSON. As all messages transmitted to and from the server include the session ID, you can reduce network usage by using a 16-byte array.
To use a session ID as a byte array in Protobuf, the server MANIFEST file
send_session_id_as_string
should be set to false. This option’s default value is true.
If you are using JSON, this option does nothing, and you should keep using 36-byte text strings.
Tip
For development convenience, the plugin’s initial setting is to request a new session ID for each connection. If you want clients to keep using the same session ID, use the Session Reliability option.
39.1.1.2. FunapiSession classes¶
FunapiSession classes were added to replace FunapiNetwork classes to use plugins more easily and conveniently.
FunapiNetwork is no longer updated. (After plugin version 158)
FunapiSession classes have most of the features of the previous FunapiNetwork classes, and interface and callback functions have been made more concise. Anyone using the previous FunapiNetwork can easily switch to FunapiSession by familiarizing themselves with a few changes.
Please refer to 클라이언트 플러그인 documentation for help with FunapiNetwork.
39.1.1.3. Session reliability options¶
Session reliability is a Boolean parameter sent when generating a FunapiSession class.
This option maintains the same session even if connection to the server is lost and restored. You can omit the authentication process required after server reconnection and keep the same user data values on the server as well. This option is recommended in environments like mobile games with unstable connections.
Tip
Session reliability can only be used with the TCP protocol.
The server and client settings must always be the same for session reliability. If the server MANIFEST
file’s use_session_reliability
option is set to true, when the FunapiSession
class is also created on the client, true must be passed to session_reliability
parameters. If these values are
different, the below log is output on the server.
// 서버는 Session reliability 옵션이 꺼져 있고 클라이언트는 켜져 있을 경우...
message with seq number. but seq number validation disabled: sid=..., transport_protocol=Tcp
// 서버는 Session reliability 옵션이 켜져 있고 클라이언트는 꺼져 있을 경우...
message without seq number: session_id=...
39.2. Connecting to a server¶
This section provides a simple introduction of how to use and implement plugins with sample code. You can see all code in the sample project included in the plugin.
39.2.1. Creating FunapiSession objects¶
This is how to generate FunapiSession classes to communicate with the server.
// 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);
Callbacks used in the plugin use Unity event objects. As in the example, register callbacks as events using the ‘+=’ operators. You can register several callbacks in one event.
The FunapiSession.Connect function in the last line uses set parameters to generate a transport and attempt connection.
If this protocol
’s transport already exists, it can be reused without reconnecting. In this case,
the option
parameter is ignored, and if reconnection is made using a particular transport, it is best to use only the FunapiSession.Connect function designated in the protocol
.
The code below declares``protocol`` and encoding
types that can be used in the FunapiSession.Connect function.
// 프로토콜은 TCP, UDP, HTTP 3가지 타입을 지원합니다.
public enum TransportProtocol
{
kDefault = 0,
kTcp,
kUdp,
kHttp
};
// 메시지 인코딩 방식은 JSON과 Protobuf 2가지 방법이 있습니다.
public enum FunEncoding
{
kNone,
kJson,
kProtobuf
}
39.2.2. Event callback function¶
FunapiSession has callback functions to report session status changes or transport status changes. There is also a separate callback function to report internal transport errors. When an error occurs in transport, the error callback and transport event callback can be called together.
39.2.2.1. Session Event¶
If the callback function is registered in SessionEventCallback, the following session-related event notifications can be received every time the session status changes.
public enum SessionEventType
{
kOpened, // 세션이 처음 연결되면 호출됩니다. 같은 세션으로 재연결시에는 호출되지 않습니다.
kChanged, // 세션 Id가 변경되면 호출됩니다. 정상적인 상황이라면 이 이벤트는 발생되지 않습니다.
kStopped, // 세션에 연결된 모든 Transport의 연결이 끊기면 호출됩니다.
kClosed, // 세션이 닫히면 호출됩니다. Transport의 연결이 끊겼다고 세션이 닫히지는 않습니다.
// 세션은 서버에서 명시적으로 Close가 호출되거나 세션 타임아웃이 되었을 때 종료됩니다.
kRedirectStarted, // Redirect 관련 이벤트는 아래 [서버간 이동] 항목을 참고해주세요.
kRedirectSucceeded,
kRedirectFailed
};
The SessionEventCallback in FunapiSession is announced as follows.
void SessionEventHandler (SessionEventType type, string session_id);
39.2.2.2. Transport Event¶
Events related to transport are as follows. Events that occur when all connections fail
or are disconnected, excluding kStarted
. kStopped
events that occur upon normal disconnection or when the connection
is closed due to an error, and always occur when transport is disconnected.
public enum TransportEventType
{
kStarted, // Transport가 시작되면 호출됩니다.
kStopped, // Transport가 종료되면 호출됩니다.
kConnectionFailed, // 연결에 실패하면 호출됩니다.
kConnectionTimedOut, // ConnectionTimeout 시간 초과로 연결에 실패하면 호출됩니다.
kDisconnected // 의도치 않게 연결이 끊겼을 때 호출됩니다.
};
kDisconnected
is invoked upon physical (socket) disconnection or ping timeout, and the point at which the physical disconnection is detected may vary widely
depending on network status or type of device. If ping is used,
it is good to judge disconnection of Disconnected events, but if ping is not used, do not
judge disconnection from Disconnected events alone; it is recommended to check through periodic
communication with the server.
The TransportEventCallback in FunapiSession is announced as follows.
void TransportEventHandler (TransportProtocol protocol, TransportEventType type);
39.2.2.3. Transport Error¶
Error type and message type in which the error occurred are sent when there is a transport error callback.
If connection was being attempted when the transport error occurred, the connection attempt is ended internally
and a kConnectionFailed
event is invoked. If the error occurred while connected to the server,
only the error is sent and the connection is not closed. However, most errors occur when connection is impossible or
when messages can’t be transmitted to and from the server, so if a transport error occurs, it is better to close that connection
and try to reconnect.
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;
}
The TransportErrorCallback in FunapiSession is announced as follows.
void TransportErrorHandler (TransportProtocol protocol, TransportError type);
39.2.3. Transport options¶
You can send transport options as parameters when invoking FunapiSession’s connect function. If this value is null, the default value is used. You need to create and designate TransportOption classes depending on the protocol to be used (TCP, UDP, HTTP). For example, if using TCP, create and send the TcpTransportOption class.
TransportOption class codes are as follows.
// Base class to specify options for Transport classes.
// Can be used for UDP transport.
public class TransportOption
{
// Specify the encryption algorithm.
// Should use the same value with the game server.
public EncryptionType Encryption = EncryptionType.kDefaultEncryption;
// Specify the compression algorithm.
// Should use the same value with the game server.
public FunCompressionType CompressionType = FunCompressionType.kNone;
// Adds a sequence number to each message to validate messages.
// If you want to validate the message without the session reliability option,
// set SequenceValidation to true.
// If you use the session reliability, it verifies the message without
// SequenceValidation option.
// This option can be specified for HTTP or TCP.
public bool SequenceValidation = false;
// 서버와 연결할 때 Timeout 될 시간을 지정합니다.
// 기본 값은 10초이며, 0을 입력할 경우 Timeout 처리를 하지 않고 계속 연결을 시도합니다.
// 이 경우, 서버로부터 응답이 오지 않으면 무한히 대기하는 상황이 발생할 수 있기 때문에
// 디버깅 목적으로만 사용하기를 권장합니다.
// Timeout에 설정한 시간을 초과하면 연결 시도를 중단하고, kStopped 이벤트를 발생시킵니다.
public float ConnectionTimeout = 10f;
}
// Transport option for TCP.
// Note that options in TransportOption class can be used.
public class TcpTransportOption : TransportOption
{
// If set to be true, it will automatically retry to connect to the server.
// (Up to 3 times.)
public bool AutoReconnect = false;
// If set to true, Nagle's algorithm will be disabled.
public bool DisableNagle = false;
// Enables ping.
public bool EnablePing = false;
// Enables log for ping values.
public bool EnablePingLog = false;
// Determines the interval between ping messages.
public int PingIntervalSeconds = 0;
// Clinet will wait for ping response from the server for PingTimeoutSeconds.
// 이 시간 내에 Ping 응답이 오지 않는다면 서버와의 연결이 끊긴 것으로 보고 Disconnect 처리됩니다.
// 인터벌 내에 핑 응답을 받지 못하면 핑을 재전송 하지 않고 핑 응답을 받은 후에 핑을 전송합니다.
public float PingTimeoutSeconds = 0f;
// Helper function to set ping-related options.
public void SetPing (int interval, float timeout, bool enable_log = false);
}
// Transport option class for HTTP.
public class HttpTransportOption : TransportOption
{
// If you want to use HTTPS, set the option.
public bool HTTPS = false;
// Determine whether to use UnityEngine.WWW class.
public bool UseWWW = false;
}
In the plugin, the HttpWebRequest class is used when an HTTP request is handled. If this function is used in Windows with Unity Editor, the editor may sometimes not respond. (A problem that occurs because the socket is not explicitly closed) A UseWWW option has been added so that UnityEngine’s WWW classes can be used to avoid this problem. As this problem only occurs in the editor, we recommend leaving this option as false by default and using a WWW class only when executing in the editor.
39.2.3.1. Websocket 옵션¶
Websocket Transport의 옵션 클래스입니다.
public class WebsocketTransportOption : TransportOption
{
// WSS 사용 여부에 대한 옵션입니다.
// 서버가 WSS일 경우 이 값을 true로 입력해야 합니다.
public bool WSS = false;
// Enables ping.
public bool EnablePing = false;
// Enables log for ping values.
public bool EnablePingLog = false;
// Determines the interval between ping messages.
public int PingIntervalSeconds = 0;
// Clinet will wait for ping response from the server for PingTimeoutSeconds.
// 이 시간 내에 Ping 응답이 오지 않는다면 서버와의 연결이 끊긴 것으로 보고 Disconnect 처리됩니다.
// 인터벌 내에 핑 응답을 받지 못하면 핑을 재전송 하지 않고 핑 응답을 받은 후에 핑을 전송합니다.
public float PingTimeoutSeconds = 0f;
// Helper function to set ping-related options.
public void SetPing (int interval, float timeout, bool enable_log = false);
}
Sample code setting transport options is in the makeOption function in the sample project Test.cs file.
39.3. Sending and receiving messages¶
39.3.1. Sending messages¶
If you want to send messages, you can call the FunapiSession class SendMessage function.
public void SendMessage (string msg_type, object message,
TransportProtocol protocol = TransportProtocol.kDefault,
EncryptionType enc_type = EncryptionType.kDefaultEncryption);
public void SendMessage (MessageType msg_type, object message,
TransportProtocol protocol = TransportProtocol.kDefault,
EncryptionType enc_type = EncryptionType.kDefaultEncryption)
public void SendMessage (int msg_type, object message,
TransportProtocol protocol = TransportProtocol.kDefault,
EncryptionType enc_type = EncryptionType.kDefaultEncryption)
msg_type
is the string type parameter showing the message type. If sending messages through Protobuf,
you can also use enum MessageType or integer value instead of string type.
message
is the object type for the message including actual data. You can send both
JSON and Protobuf messages.
If protocol
is not set, messages are sent in the initial protocol used to
connect to FunapiSession. If you have several transport protocols registered and want to send a message using a particular one,
designate the protocol value.
If you want to set a default protocol when sending messages with connections to FunapiSession through several protocols, you can modify FunapiSession’s DefaultProtocol properties.
session.DefaultProtocol = TransportProtocol.kHttp;
enc_type
is the encryption type. If the type of encryption used is not set,
the default type is used, and an error occurs if this value is entered when encryption is not used.
If several types of encryption are allowed in one protocol on the server, you can select the encryption type to be used when sending messages. To learn more about encryption, please read Message encryption.
39.3.1.1. Sending JSON messages¶
Dictionary<string, object> message = new Dictionary<string, object>();
message["message"] = "hello world";
session.SendMessage("echo", message);
The plugin uses MiniJSON library for JSON. The MiniJSON data type is Dictionary<string, object>. If necessary, you can use other JSON libraries. To learn more about this, please read:ref:client-plugin-json-helper.
39.3.1.2. Sending Protobuf messages¶
PbufEchoMessage echo = new PbufEchoMessage();
echo.msg = "hello proto";
FunMessage message = FunapiMessage.CreateFunMessage(echo, MessageType.pbuf_echo);
session.SendMessage("pbuf_echo", message);
//
// 메시지 타입으로 enum MessageType 타입을 사용할 수 있습니다.
// session.SendMessage(MessageType.pbuf_echo, message);
// 또는 해당 타입의 정수값을 직접 사용해도 됩니다.
// session.SendMessage(16, message);
...
Protobuf messages are FunMessage by default. User messages are in extended form. Extended fields from 0-15 are reserved. User messages can use fields starting at 16.
MessageType is the enum list also created when making message DLL by building .proto files on the server.
예를 들어 서버에서 다음과 같이 메시지를 정의했다면,
message PbufEchoMessage {
required string msg = 1;
}
extend FunMessage {
optional PbufEchoMessage pbuf_echo = 16;
...
}
아래와 같이 MessageType 이 생성됩니다.
public enum MessageType
{
...
pbuf_echo = 16,
}
With this, you can use message names instead of numbers for extended messages.
Important
To use Protobuf-net messages in Unity, you need to follow an additional build process. See Using Protobuf in Unity for an explanation.
39.3.1.3. Dropped 메시지¶
메시지를 보낼 수 없는 상태에서 메시지 전송 시 해당 메시지는 전송되지 않고 버려집니다. 이 때 DroppedMessageCallback 이 등록되어 있다면 이 콜백으로 버려진 메시지를 전달합니다.
메시지를 보낼 수 없는 상태는 아래와 같습니다.
리다이렉트 중이고 리다이렉트 큐를 사용하지 않을 때
메시지를 보내려는 프로토콜의 트랜스포트가 없을 때
메시지를 보내려는 프로토콜의 트랜스포트가 Reliable (Session Reliability + TCP) 하지 않고 연결된 상태가 아닐 때
메시지를 보내려는 트랜스포트에 설정된 인코딩(Protobuf)과 보내려는 메시지 형식(FunMessage)이 일치하지 않을 때
DroppedMessageCallback 은 다음과 같이 선언되어 있습니다.
public delegate void DroppedMessageCallback(string msg_type, object message);
데이터형이 object인 이유는 받는 메시지가 JSON일 수도 있고 Protobuf 메시지일 수도 있기 때문입니다.
등록한 핸들러에서 메시지 타입에 따라 message
객체를 JSON이나 Protobuf로 캐스팅해서 사용하면
됩니다.
39.3.2. Message reception¶
To receive messages from the server, you need to register a message callback function. ReceivedMessageCallback is declared as follows.
void ReceivedMessageHandler (string msg_type, object message);
The reason the data type is an object is because messages can be JSON or Protobuf.
You can cast message
objects as JSON or Protobuf depending on the message type registered in the
handler.
An example of a message handler follows. All code can be found in the Tester.Session.cs file.
// 받은 메시지를 처리할 콜백을 등록합니다.
session.ReceivedMessageCallback += onReceivedMessage;
// 기다리는 메시지에 timeout을 정해서 알림을 받고 싶다면 이 콜백을 등록합니다.
// 메시지가 지정한 시간내에 오지 않으면 이 콜백 함수가 호출됩니다.
session.ResponseTimeoutCallback += onResponseTimedOut;
// 메시지를 처리하는 방식은 정해진 형식이 없으며 사용하기 편한 형태로 쓰시면 됩니다.
// 여기에서는 받은 메시지를 처리하기 위해 각각의 메시지를 Key-Value 방식으로 저장하겠습니다.
message_handler_["echo"] = onEcho;
message_handler_["pbuf_echo"] = onEchoWithProtobuf;
Since already deserialized messages are sent with the ReceivedMessageCallback function inside the plugin, there is no need to deserialize them again. Just cast received object messages to use.
Implementation of a message callback function registered in message_handler_
is as follows:
// 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);
}
39.3.3. Response Timeout¶
If you want to set a timeout for awaiting messages from the server, you can use the SetResponseTimeout function in FunapiSession classes to designate your desired message timeout period.
// 기다리는 메시지 타입과 시간을 지정합니다. 등록되는 순간부터 시간이 적용됩니다.
// 'sc_login' 메시지가 10초 내에 오지 않을 경우 ResponseTimeoutCallback 함수가 호출됩니다.
session.SetResponseTimeout("sc_login", 10f);
Tip
You cannot register multiples of the same message type.
If you want to reset timeout for the same messages for a one-time event, you need to invoke ReceivedMessageCallback once the message has been received or the ResponseTimeoutCallback function once the message has timed out, then set timeout again with the SetResponseTimeout function.
ResponseTimeoutCallback is declared as follows. Message type transmitted with the SetResponseTimeout function is received as a parameter. If that message is received from the server within the timeout period, this callback function is not invoked.
public delegate void ResponseTimeoutHandler (string msg_type);
39.3.4. JSON helper classes¶
MiniJSON is the default plugin. If you want to use another JSON library, have it inherit the JsonAccessor interface class to create a class to handle the JSON library by changing JsonHelper in FunapiMessage.
If the necessary function exists but is not in the JasonAccessor class, add it to the JsonAccessor derived class to use.
The newly created JsonAccessor class can be registered as follows: Messages sent and received later are all serialized or deserialized through the newly registered JSON class.
NewJsonAccessor json_helper = NewJsonAccessor();
FunapiMessage.JsonHelper = json_helper;
Note
The JsonAccessor class interface was changed in plugin version 164. (August 2016 update) If JsonHelper was implemented after inheriting the JsonAccessor class, you need to additionally implement it on interfaces added during plugin update.
39.4. Message encryption¶
Messages transmitted to and from the server can be encrypted.
39.4.1. Encryption types¶
Encryption types are as follows:
public enum EncryptionType
{
// 암호화를 사용하지만 특정 메시지를 암호화하지 않은 상태로 보내고 싶을 때
// SendMessage의 파라미터로 이 값을 전달하면 됩니다.
kDummyEncryption,
// iFun Engine에서 제공하는 암호화 타입입니다.
// 메시지를 주고 받을 때마다 키 값이 변경되어 안정적인 암호화 방식입니다.
// Tcp 프로토콜에서만 사용 가능합니다.
kIFunEngine1Encryption,
// iFun Engine에서 제공하는 암호화 타입입니다.
// 고정된 암호화 키를 사용합니다. 프로토콜에 상관없이 사용 가능합니다.
kIFunEngine2Encryption,
// ChaCha20 암호화 타입입니다.
// Tcp 프로토콜에서만 사용 가능합니다.
kChaCha20Encryption,
// Aes 128 암호화 타입입니다.
// Tcp 프로토콜에서만 사용 가능합니다.
kAes128Encryption
}
If encryption is set, all messages are sent encrypted, but some messages may be sent as-is without encryption if you wish. If you want to use encryption but want to send particular messages without it, set SendMessage encryption type to kDummyEncryption.
The kIFunEngine1Encryption type changes the encryption key each time messages are transmitted. This is a safe encryption method as the encryption key values are different every time, even for the same message. kIFunEngine2Encryption uses an inherent encryption key, so it is a relatively weaker encryption type than kIFunEngine1Encryption.
Tip
IFunEngine1, ChaCha20, and AES-128 encryption can only be used in the TCP protocol.
ChaCha20 and Aes128 are used with external libraries (Sodium). Files for this library are in the Plugins folder.
39.4.2. Using encryption¶
When TCP messages are encrypted on the server without any other setting needed, this is followed on the client. When the server is first contacted, encryption is synchronized, and when the server sends the relevant protocol’s encryption type list, the client uses the encryption method sent by the server.
If there are several types of encryption, the first type is used as the default. If no particular encryption type is set when a message is sent, the default type of encryption is used. If you want to set another default encryption type, you can set the TransportOption encryption value when the FunapiSession.Connect function is called..
TcpTransportOption option = new TcpTransportOption();
option.Encryption = EncryptionType.kIFunEngine1Encryption;
If the client uses a type of encryption not used by the server, an error occurs and messages cannot be transmitted.
For UDP and HTTP, there is no encryption type synchronization with the server after contact, so the encryption type needs to be set on the client. The only type of encryption provided by iFun Engine that can be used by UDP and HTTP is IFunEngine2Encryption. If you want to encrypt in UDP or HTTP, set this type in TransportOption encryption.
TransportOption option = new TransportOption();
option.Encryption = EncryptionType.kIFunEngine2Encryption;
Note
The client plugin distributed via GitHub does not include IFunEngine1Encryption and IFunEngine2Encryption and is a free version. You can use ChaCha20 or AES-128. Paid customers can email iFun Engine support to request a plugin source that includes all encryption types.
39.5. Message Compression¶
Message sent between client and server can be compressed. Currently, following algorithms are supported.
FunCompressionType.kNone
: Default value. Does not compress messages.FunCompressionType.kZstd
: Zstadnard. Recommended for real-time messages.FunCompressionType.kDeflate
. Deflate. It offers better compression ratio, but it may incur additional latency.
39.6. Closing connections and reconnecting¶
39.6.1. Closing connections¶
To close a connection to the server, you can invoke the FunapiSession stop function.
session.Stop();
The stop function without parameters disconnects all connected transports. If you want to disconnect a particular transport, you can send the protocol type as a parameter.
session.Stop(TransportProtocol.kTcp);
The stop function is an asynchronous method that disconnects all connected transports. The reason connections are immediately closed when stop is invoked is that queued or unsent messages left in the buffer when a transport is attempting to connect to the server are sent and then the connection is closed.
Note
The reason a transport waits when connecting to the server is that if stop is invoked during connection, iOS crashes occur due to an IL2CPP-related bug.
The function that sends unsent messages before the connection closes is only supported in TCP. If SendMessage is invoked or unsent messages are left during a TCP connection, remaining messages are all sent and then the connection is closed. Thus, even if messages like logout are sent immediately before invoking stop, they are guaranteed to be sent.
There are some cases in which the connection is closed without waiting: if the session is closed from the server, if a session must be closed for movement between servers, or if an app is closed.
FunapiSession 의 CloseRequest 함수를 호출하여 서버가 세션을 닫도록 요청할 수도 있습니다.
session.CloseRequest();
서버가 클라이언트의 세션 종료 요청을 받으면 세션을 종료하고 세션이 닫혔다는 메시지를 클라이언트로 전송합니다.
Stop 함수는 서버와의 연결을 종료하지만 서버의 세션에는 영향을 미치지 않기 때문에 서버에는 세션이 남아있는 상태로 유지됩니다. 서버와 연결을 종료하면서 서버의 세션도 닫고 싶다면 CloseRequest 함수를 사용해야 합니다.
39.6.2. Disconnection notification¶
39.6.2.1. Transport Event¶
When a transport connection is closed, kStopped is sent as a transport event. These events always occur when a transport connection is closed, regardless of whether it is closed due to an error or whether there is a normal disconnection.
If a transport is unintentionally disconnected, a kDisconnected event is sent as a transport event. If using ping, connection to the server is checked, and if there is no server response during a set period of time, a kDisconnected event occurs. If not using ping, it is unreasonably difficult to check disconnection in varied mobile environments through this event only. This is because it often takes a long time to detect disconnection, depending on the device and network type. If you want to check server connection using a kDisconnected event, you should use ping.
Tip
kDisconnected events do not occur in the HTTP protocol. Ping can only be used for TCP.
You can register FunapiSession’s TransportEventCallback to get transport events.
39.6.2.2. Session Event¶
If all transports connected to a session are disconnected, a kStopped event is sent as a session event.
Session kClosed events do not occur from transport disconnections, but from session timeout or when the server explicitly ends the session. When a message saying a session is closed is sent from the server, all transports are disconnected and a kClosed event is sent.
You can register FunapiSession’s SessionEventCallback to get session events.
39.6.3. Reconnecting¶
Use the FunapiSession.Connect function to reconnect after a server connection is closed. You just need to send the transport protocol type to connect with as a parameter.
public void Connect (TransportProtocol protocol)
You can use the connect function, including the TransportOption parameter, to reconnect, but you cannot change transport options once registered and, even if a new TransportOption is sent, it is ignored. Thus, it is advisable to set only the protocol to invoke the connect function during reconnection.
For TCP, even if the AutoReconnect
option is set to true
, reconnection is not attempted
if the reconnection attempt fails. AutoReconnect
is only used for the first connection.
39.7. Using ping¶
There is a command called ping used on the terminal to check network connection status. There is a similar ping function in the plugin as well. You can use it to check whether connection to the server is maintained.
You can continuously check connection status using this ping function in unpredictable mobile network environments. Thus, you can more quickly assess and respond to disconnections.
Tip
이 기능은 TCP 프로토콜과 Websocket 프로토콜에서 사용 가능합니다.
39.7.1. Ping configuration¶
Ping can be set as a transport option before invoking the FunapiSession.Connect function. To use ping, invoke the SetPing function as follows or enter each individual value.
TcpTransportOption option = new TcpTransportOption();
// WebsocketTransportOption option = new WebsocketTransportOption();
// 이 함수만 호출하면 핑 설정이 완료됩니다.
option.SetPing(3, 20, true);
...
// 각각의 값을 따로 입력하고 싶다면 아래와 같이 하면 됩니다.
// 이 값을 true로 주면 Ping 기능을 사용할 수 있습니다.
// EnablePing의 기본 값은 false 입니다.
option.EnablePing = true;
// 핑 값을 로그로 보고 싶다면 이 값을 true로 주면 됩니다.
option.EnablePingLog = true;
// 핑 메시지를 보내는 간격을 지정합니다. (초단위)
option.PingIntervalSeconds = 3;
// 서버로부터 핑 응답을 기다리는 최대 시간을 지정합니다. (초단위)
// 이 시간 이내에 응답이 없을 경우 연결을 끊고 Disconnect 처리를 하게 됩니다.
option.PingTimeoutSeconds = 20;
If there is no response at all from the server during the timeout period, the connection is closed and handled as a disconnection. When disconnected, kDisconnected is sent as a FunapiSession TransportEventCallback.
You can learn how to activate ping on the server in Session Ping (RTT).
39.8. Server redirect¶
In some cases, it may be necessary to disconnect an active connection and access a new server. This process is handled in the plugin and is a server-side request, so there is no need for the client to request a server move.
39.8.1. Checking server redirect¶
When transfer between servers begins, its status is sent through SessionEventCallback. Below is an event type received through a callback.
public enum SessionEventType
{
...
kRedirectStarted, // 서버간 이동을 시작합니다.
kRedirectSucceeded, // 서버간 이동을 완료했습니다.
kRedirectFailed // 서버간 이동에 실패했습니다.
};
When movement between servers begins, a kRedirectStarted
event is sent and the current server connection is closed.
A new server connection begins. Until movement between servers is finished, session or transport events,
excluding redirect events, do not occur. Even if there is an error during connection, only a debug log
is output and no events occur. When movement between servers is complete, kRedirectSucceeded
or
kRedirectFailed
events are sent.
39.8.2. 서버 이동에 사용할 옵션 설정¶
서버를 이동할 때 접속 중인 서버와 이동하는 서버 간의 설정이 다를 수 있습니다. 이럴 경우 클라이언트에서 Session / Transport 의 옵션을 직접 설정해야 합니다. 옵션 값을 설정하지 않은 경우 이동 전 Session 과 Transport 의 옵션을 그대로 사용하게 됩니다. 옵션 값을 전달하는 콜백 함수의 원형은 아래와 같습니다.
public delegate SessionOption SessionOptionHandler (string flavor);
public event SessionOptionHandler SessionOptionCallback;
public delegate TransportOption TransportOptionHandler (string flavor, TransportProtocol protocol);
public event TransportOptionHandler TransportOptionCallback;
typedef std::function<std::shared_ptr<FunapiSessionOption>(
const fun::string& /*flavor*/)> SessionOptionHandler;
void SetSessionOptionCallback(const SessionOptionHandler &handler);
typedef std::function<std::shared_ptr<FunapiTransportOption>(
const TransportProtocol,
const fun::string& /*flavor*/ )> TransportOptionHandler;
void SetTransportOptionCallback(const TransportOptionHandler &handler);
flavor
는 서버의 종류를 나타냅니다. 이 타입은 서버에서 정해주는 문자열 값입니다. 서버로부터
이동 메시지를 받으면 이동할 서버의 Transport를 새로 만들기 전에 이 콜백 함수들을 호출합니다. 해당 서버의
종류와 프로토콜 타입에 따라 아래와 같이 옵션을 구성해서 반환하면 됩니다.
Note
이동 전 서버에 동일한 프로토콜의 Transport 가 없을 경우에는 기본 TransportOption 을 사용합니다.
session_.SessionOptionCallback += onSessionOption;
session_.TransportOptionCallback += onTransportOption;
SessionOption onSessionOption (string flavor)
{
if (flavor == "lobby")
{
SessionOption option = new SessionOption();
option.sessionReliability = true;
return option;
}
// 서버 이동전 옵션을 그대로 사용합니다.
return null;
}
TransportOption onTransportOption (string flavor, TransportProtocol protocol)
{
if (flavor == "lobby" && protocol == TransportProtocol.kTcp)
{
TcpTransportOption tcp_option = new TcpTransportOption();
tcp_option.DisableNagle = true;
return tcp_option;
}
// 서버 이동전 옵션을 그대로 사용합니다.
return null;
}
// "lobby" flavor 를 사용하는 서버에 접속 할때 세션 신뢰성 옵션을 활성화 합니다.
session_->SetSessionOptionCallback([](const fun::string &flavor) -> std::shared_ptr<fun::FunapiSessionOption>{
if (flavor.compare("lobby") == 0) {
auto session_option = fun::FunapiSessionOption::Create();
session_option->SetSessionReliability(true);
return session_option;
}
// 서버 이동전 옵션을 그대로 사용합니다.
return nullptr;
});
// 새로 만들어질 Tcp 프로토콜의 옵션을 설정합니다.
session_->SetTransportOptionCallback([](const fun::TransportProtocol protocol,
const fun::string &flavor) -> std::shared_ptr<fun::FunapiTransportOption> {
if (flavor == "lobby" &&
protocol == fun::TransportProtocol::kTcp) {
auto option = fun::FunapiTcpTransportOption::Create();
option->SetDisableNagle(true);
return option;
}
// 서버 이동전 옵션을 그대로 사용합니다.
return nullptr;
});
이미 사용중이던 옵션과 동일한 옵션을 사용하거나 기본 옵션으로 연결해도 되는 경우에는 굳이 이 이벤트 콜백을 등록할 필요는 없습니다.
39.9. Multicasting and chatting¶
You can use the plugin’s multicasting feature to connect to a desired channel and transmit messages to and from all users on that channel. FunapiSession objects are used to connect and send messages to the server handling multicasting. You can create FunapiMulticastClient objects while sending FunapiSession objects to use multicasting.
Use FunapiMulticastClient to send messages to all users on the same
channel. If _bounce
is true, the message can also be received by the sender.
FunapiChatClient classes especially for chatting are derived classes that inherit FunapiMulticastClient. FunapiChatClient is good for chatting between players using classes that only transmit text.
멀티캐스팅은 TCP 또는 Websocket 을 사용 하며, Transport 를 따로 설정하지 않을 경우 디폴트 값을 사용합니다. And encoding follows the method used in the connected FunapiSession. You can use JSON and Protobuf.
Important
You can only connect to one object per session in multicasting. If you connect to two or more multicasting objects, it is impossible to tell which object the message must be sent to when a multicast message is received. FunapiMulticastClient and FunapiChatClient classes cannot both be used simultaneously in the same session.
39.9.1. Multicasting interface¶
The following interfaces are in FunapiMulticastClient.
// FunapiMulticastClient 생성자입니다.
// FunapiSession와 주고 받을 메시지의 Encoding을 입력받습니다.
public FunapiMulticastClient (FunapiSession session,
FunEncoding encoding,
TransportProtocol protocol = TransportProtocol.kDefault)
// 내 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);
39.9.2. Example of multicasting¶
To prepare in advance to handle multicasting with FunapiMulticastClient, first create a FunapiSession and connect using TCP.
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 will then be made to send messages.
You can register a callback function to notify of channel entry or exit. Notifies the coming or going of all users on the channel, including you.
JoinedCallback |
When a user joins the channel, the function invoked is |
LeftCallback |
When a user leaves a channel, the function invoked is |
ErrorCallback |
When an error appears, the function sends the error code |
// 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
};
Invokes the JoinChannel()
function to join a channel. When a message is sent from the channel, the
onMulticastChannelReceived function is invoked.
To join several channels at once, invoke the JoinChannel()
function for each channel you wish to
join.
// 입장할 채널 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
is in the server’s {project}_messages.proto file.
When the first project is created with funapi_initiator, this is added by default. PbufHelloMessage
is created for the example, and proto message names and fields on the MulticastServer
are unlimited. When you create a proto message with the name you need and define the fields to use,
iFun Engine recognizes these and sends them to the client. This is the same with JSON.
Default data is in the following server’s {project}_messages.proto file.
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;
}
In the following onMulticastChannelReceived Protobuf sample code, the message called MulticastMessageType.pbuf_hello
is not added to the plugin by default. The proto file is also built when you build the server.
At that time the PBufHelloMessage field names and extended numbers entered into
extend FunMulticastMessage
are automatically
recognized and enum is created. For more on this, please refer to Using Protobuf in Unity.
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);
}
}
To send a message, invoke the SendToChannel()
function.
// 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);
}
When leaving the channel, the LeaveChannel()
function is invoked.
// 채널 나가기
string channel_id = "test_channel";
multicast.LeaveChannel(channel_id);
39.9.3. Chatting interface¶
The following interfaces are available in FunapiChatClient, and the difference from FunapiMulticastClient
is that messages are not received as object
objects and only string
are received.
// FunapiChatClient 생성자입니다.
// FunapiSession과 주고 받을 메시지의 Encoding을 입력받습니다.
public FunapiChatClient (FunapiSession session,
FunEncoding encoding,
TransportProtocol protocol = TransportProtocol.kDefault);
// 채널에 입장할 때 사용하는 함수입니다.
// 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);
39.9.4. Example of chatting¶
FunapiChatClient uses the multicasting function, and the classes created are used very similarly to those in FunapiMulticastClient. Therefore, we will acquaint you here with basic use of FunapiChatClient.
Json
is used for encoding in the example.
// 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);
When messages are received, the onMulticastChannelReceived()
function is invoked.
// 메시지를 전달받으면 호출됩니다.
// 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);
}
Use the SendText()
function to send messages to the channel.
// 채널에 메시지 전송
string channel_id = "test_channel";
string message = "Hello World";
chat.SendText(channel_id, message);
When leaving the channel, the LeaveChannel()
function is invoked.
// 채널 나가기
string channel_id = "test_channel";
chat.LeaveChannel(channel_id);
39.10. Checking announcements¶
If you use the announcement server provided by the Engine, you can get announcement lists from the announcement server through a client plugin and update them whenever you want.
You can request announcement lists from the server as the FunapiAnnouncement class.
public class FunapiAnnouncement
{
// 공지사항 서버의 ip와 port 번호를 주소로 초기화 합니다. ex)"http://127.0.0.1:8080"
public void Init (string url);
// 공지사항 목록을 가져옵니다. 카테고리와 페이지를 지정할 수 있습니다.
public void UpdateList (int max_count, int page = 0, string cateogry = "");
// 받아온 공지사항의 개수를 출력합니다.
public int ListCount;
// 각 공지사항의 정보를 가져옵니다.
public Dictionary<string, object> GetAnnouncement (int index);
// 각 공지사항의 메인 이미지 경로를 가져옵니다.
public string GetImagePath (int index);
// 각 공지사항의 추가 이미지 경로들을 가져옵니다.
public List<string> GetExtraImagePaths (int index);
// 각 공지사항의 모든 이미지 경로들을 가져옵니다.
public List<string> GetAllImagePaths (int index);
}
Note
한 페이지 당 공지사항 개수는 max_count 와 같습니다. 예를 들어 UpdateList(3, 0)
을 호출할 경우
최근 공지사항(첫 페이지)을 최대 3개 가져옵니다. UpdateList(3, 1)
을 호출할 경우 최근 공지사항 3개 이후의
공지사항(다음 페이지)부터 최대 3개를 가져옵니다.
Note
공지사항에 이미지가 포함되어 있을 경우 공지사항을 다 받은 후에 순차적으로 다운로드 하여 image_url
과 extra_images
에 들어 있는
파일을 플랫폼 별로 다음과 같은 위치에 저장합니다.
FunapiUtils.GetLocalDataPath/
+ "announce/"
경로 아래에 저장합니다.FunapiAnnouncement::Create(url, path)
함수의 path
인자로 지정된 경로 아래에 저장합니다.Note
모든 다운로드가 끝나면 콜백 함수가 호출됩니다. 다운로드 할 이미지가 많을 경우 콜백 함수 호출까지 시간이 오래걸릴 수도 있습니다.
The following shows sample code to request announcements from the server using the FunapiAnnouncement class and parse the announcement list in the callback function. The message type is JSON.
FunapiAnnouncement announcement = new FunapiAnnouncement();
void Init ()
{
// 공지사항은 웹 서비스입니다.
// 서버에서 공지사항 용도로 열어 둔 ip와 port를 주소로 요청합니다.
announcement.Init("http://127.0.0.1:8080");
announcement.ResultCallback += OnAnnouncementResult;
// 이 함수를 호출하면 목록을 가져오거나 이미 있을경우 갱신해줍니다.
// 가져올 개수와 페이지, 카테고리를 인자로 넘깁니다.
announcement.UpdateList(5, 0, "category");
}
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);
}
}
}
std::shared_ptr<fun::FunapiAnnouncement> announcement_ = nullptr;
// 공지사항은 웹 서비스입니다.
// 서버에서 공지사항 용도로 열어 둔 ip와 port를 주소로 요청합니다.
// 두번째 인자로 다운로드될 파일들의 저장위치를 지정합니다.
announcement_ = fun::FunapiAnnouncement::Create("http://127.0.0.1:8080",
TCHAR_TO_UTF8(*(FPaths::ProjectSavedDir())));
// 완료 결과를 받을 콜백을 등록합니다.
announcement_->AddCompletionCallback([this](const std::shared_ptr<fun::FunapiAnnouncement> &announcement,
const fun::vector<std::shared_ptr<fun::FunapiAnnouncementInfo>>&info,
const fun::FunapiAnnouncement::ResultCode result){
if (result == fun::FunapiAnnouncement::ResultCode::kSucceed)
{
// 각각의 목록에 대해 정보를 가져올 수 있습니다.
for (auto i : info)
{
UE_LOG(LogFunapiExample,
Log,
TEXT("date=%s message=%s subject=%s file_path=%s"),
*FString(i->GetDate().c_str()),
*FString(i->GetMessageText().c_str()),
*FString(i->GetSubject().c_str()),
*FString(i->GetFilePath().c_str()));
}
}
});
// 서버로 부터 받을 공지사항 개수를 등록합니다.
announcement_->RequestList(5);
// 목록을 가져오거나 이미 있을경우 갱신해줍니다.
fun::FunapiAnnouncement::UpdateAll();
There are no strict rules about what can be included in an announcement. The server administrator can define and use whatever items are needed. Note that if fields in use are changed, the plugin code may also need to be modified, so if you need to change existing fields, contact iFun Engine support.
39.11. Server maintenance messages¶
During server maintenance, messages sent by clients are ignored and the server sends a notification to the client informing it of server maintenance each time a message from it is received. If no messages are sent to the server after server connection, maintenance notifications sent by the server are not received.
Maintenance notifications are sent using FunapiSession’s MaintenanceCallback function rather than the usual message receipt callback. To handle server maintenance notifications, you can register the MaintenanceCallback function and conduct the required tasks in the callback function.
// 서버로부터 점검 안내 메시지를 받았을 때 이 콜백함수가 호출됩니다.
session.MaintenanceCallback += onMaintenanceMessage;
You can parse and use server maintenance messages as follows. Message data includes the maintenance start time, end time, and a text string message.
date_start |
string |
Server maintenance start time |
date_end |
string |
Server maintenance end time |
messages |
string |
Messages |
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);
}
All code can be found in the Tester.Session.cs file.
39.12. Downloading resource files¶
If the server uses a client resource service, client resources can be updated with no other binary update on the client end.
You need to use the FunapiHttpDownloader class to use this function. The following sample code illustrates creation of FunapiHttpDownloader and registration of the callback function. All code can be found in the Tester.Download.cs file.
// 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");
ReadyCallback
and FinishedCallback
are essential callback functions, while the others can be
registered and used as needed.
Download begins when the file list is received. Invoke the GetDownloadList function to get the resource list and see local files. Set the server URL and folder path to save the file in the GetDownloadList function parameters. file/path is the file path including the list file name. Only the last part needs to be sent while leaving out the first parameter, the server URL address. These values don’t need to be set if requested from the game server. However, resource list files must be set if requested from a CDN.
VerifyCallback |
Invoked when verification of each file ends. |
ReadyCallback |
Invoked when download preparations are complete after the resource list is received and files have been verified. |
UpdateCallback |
Notifies of file download progress. Should be used for UI updates. |
FinishedCallback |
Invoked when resources have been downloaded. |
When the download is ready after the GetDownloadList function is invoked, ReadyCallback is invoked. After the ReadyCallback function is invoked, StartDownload is invoked and the download can begin. When the download finishes, the FinishedCallback function is invoked.
downloader.StartDownload();
If there are no new files to download, ReadyCallback is not invoked and only FinishedCallback is invoked.
There are a few things to check before starting the download in the FunapiHttpDownloader class.
TotalDownloadFileCount |
Total number of files to download |
TotalDownloadFileSize |
Total download file size (bytes) |
CurrentDownloadFileCount |
Number of files finished downloading |
CurDownloadFileSize |
Size of downloaded files (bytes) |
These properties must be used after ReadyCallback has been invoked.
39.14. Unity plugins¶
Download the Unity client plugin from GitHub. If you need a version that includes encryption, you need to request it from iFun Engine support.
You can copy the Funapi and Plugins folders from among the files in the Assets
folder in the source code received from GitHub
and paste them in the Assets
folder of the project where you will use the plugin. If using HTTPS, you should also copy
the Editor and Resources folders. Sample plugin files are in the Tester
folder.
39.14.1. Using Protobuf in Unity¶
Due to iOS/Android limitations, you cannot directly use C# code made with protobuf-net.
If you use C# files made with protobuf.NET
, error messages like the following will be left in
some_protobuf_message.GetExtension(...)
code
and libmono
’s JIT compiler will crash.
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)
Therefore, you need to change the message serialization/deserialization portion to .dll built by AOT, not C# code.
Setting up .dll builds for Protobuf
You can see the following setting called GENERATE_UNITY_PROTOBUF_DLL
in
CMakeLists.txt
in the game server’s top-level directory. If this value is true
, the necessary .dll file is created
in the build directory.
# 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)
Copying created .dll files
For example, suppose that you built the project named hello with debug configuration.
The following .dll files would be generated in the hello-build/debug/unity_dll
directory.
protobuf-net.dll
:protobuf-net
file for Unity
messages.dll
: Protobuf message definition created by the engine
FunMessageSerializer.dll
: Protobuf reading/writing routine created by the engine
You can copy this file to the client’s Assets
directory and see it in Unity Editor.
In Windows, it is easiest to use the program provided by
https://winscp.net/eng.
Tip
Individual messages and source code for messages’ enum
is created as messages.cs
and messages_enum.cs
files in the unity_cs
directory under the build directory.
39.14.2. C# Runtime Test Code¶
There is C# Runtime test code to test plugin bots inside the plugins folder. Since Unity limits the maximum number of sockets that can be open at once, we recommend writing a bot program to test. More details can be found in Approach 2: Leveraging the client plug-in.
39.14.3. Viewing Unity plugin logs¶
Plugin logs are only visible in Unity Editor or while C# Runtime is running. Since even leaving logs can affect a game’s performance, logs are not left other than when running in the editor.
// 유니티 에디터 or C# Runtime일 경우 로그 표시
#if UNITY_EDITOR || NO_UNITY
// 기본적인 로그 출력
#define ENABLE_LOG
// 디버깅에 도움이 되는 로그 출력. ENABLE_LOG 가 선언되어 있어야 사용 가능합니다.
#define ENABLE_DEBUG
#endif
테스트를 위해 플러그인의 로그를 항상 보고 싶다면 Funapi/DebugUtils.cs 파일의 ENABLE_LOG
가 항상 정의되도록 수정하거나 빌드 세팅에서 define symbol 을 추가하면 됩니다. 빌드 세팅의
Other Settings 탭에서 Scripting Define Symbols 항목에 ENABLE_LOG 를 추가하면 됩니다.
추가로 ENABLE_DEBUG 심볼을 정의해서 디버깅에 도움이 되는 로그 출력 할 수 있습니다.
ENABLE_LOG 가 선언되어 있어야 사용 가능합니다.
39.14.4. Saving logs¶
You can save plugin logs as text strings or files. To use this feature,
define ENABLE_SAVE_LOG
in the Funapi/DebugUtils.cs file.
The following functions and properties have to do with log storage in the FunDebug class. Sample code for this is in the Tester/Tester.cs file.
// 이 옵션을 활성화하면 로그가 버퍼에 저장됩니다.
#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();
}
39.14.5. Creating a debug log file¶
If there is deemed to be a problem in the Unity plugin during development, please save a log as follows to send us the file and let us know the steps to reproduce the problem.
Define the following in the Funapi/DebugUtils.cs file.
#define ENABLE_DEBUG
#define ENABLE_SAVE_LOG
Invoke the following function to save a log when the game closes or immediately after the reproducible event occurs.
// 로그를 파일로 저장
FunDebug.SaveLogs();
If running in the editor, these saved logs can be found in the Data/Logs
folder in the same path as the project folder
where Assets
is located. If testing on a mobile device, access to the app’s save folder
is limited on the device.
39.15. Unreal Engine 4 plugins¶
Download the Unreal Engine 4 plugin from GitHub. If you need a version that includes encryption, you need to request it from iFun Engine support.
39.15.1. Executing in the editor¶
Simple sample code to test FunapiSession
is included in the plugin. Objects in this actor class
must be displayed in the Unreal Editor for the tester
sample map to play without errors. When the project
is first run, funapi_tester objects may not be displayed in the editor. If this occurs, compile the project to
make class objects. If they aren’t displayed in the editor even after compiling, close the editor,
then reload the project. If funapi_tester
objects appear in the editor’s content browser (C++ Class > funapi_plugin_ue4),
load the tester
map, then press the play button to execute the sample program.
39.15.2. Compiling external libraries¶
Use the following external libraries with the Unreal plugin.
libcurl |
Uses necessary functions for HTTP communication. |
libcrypto |
Encryption library included in the OpenSSL library. Uses MD5. |
libprotobuf |
The Google protocol buffer library. |
libsodium |
ChaCha20 The Library for AES-128 encryption. |
Script for compiling the libraries above is distributed along with compiled library files. Build scripts and library files inside the ThirdParty folder within the plugin folder are differentiated by platform.
ThirdParty
ㄴ build // 라이브러리 빌드 스크립트 파일들
ㄴ include // 헤더 파일들
ㄴ lib // 라이브러리 파일들
ㄴ proto // .proto 파일과 proto 파일용 컴파일 스크립트
You can build and use libraries as distributed scripts for compiling, but if there is no particular reason to rebuild a library, you can use distributed library files as-is.
39.15.3. Building Protobuf files¶
To build .proto
files, execute ThirdParty/proto/make-win.bat
or make-mac.sh
files.
To add .proto
files to the build list, open the batch files and add them to the file list at the bottom. If you run batch files, the output file is added/modified
in the funapi
folder inside the Source
folder.
39.15.4. 언리얼 엔진과 관련한 알려진 문제¶
언리얼 엔진의 제약 사항이나 아직 수정되지 않은 문제로 인해서 Funapi 플러그인 사용 시 발생할 수 있는 문제들 대해서 해결방법을 알려드립니다.
아래 내용을 확인 해 보시고, 추가 문의사항은 iFun Engine support 으로 보내 주시면 성실히 답변 드리겠습니다.
39.15.4.1. 버전 4.21 : .cc
파일 컴파일 오류¶
언리얼 엔진 4.21 버전에서 .cc 확장자를 갖는 파일을 포함해서 컴파일 하는 경우 아래와 같은 메시지를 출력하면서 컴파일 오류가 발생합니다. 언리얼 이슈 링크
UnrealBuildTool : error : Was only expecting C++ files to have
CachedCPPEnvironments!
Funapi UE4 플러그인을 사용하면 플러그인이 포함하는 .cc 확장자 때문에 문제가 발생하며, Unreal Engine 4.22 이후 버전에서는 발생하지 않습니다.
funapi-plugin-ue4 프로젝트와 함께 제공하는 ChangeExtensions.{bat|sh} 스크립트를 운영체제에 맞게 실행하면 .cc 파일의 확장자를 .cpp 파일로 변경할 수 있습니다. 또는 직접 찾아서 수정해도 됩니다.
각 빌드 시스템 타깃 파일에 bUseUnityBuild 옵션을 비활성화 합니다.
File name : ${Project_root}/funapi_plugin_ue4Editor.Target.cs
Description : 샘플 프로젝트의 Editor 빌드 시스템 설정파일입니다.
public funapi_plugin_ue4EditorTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
// UnityBuild 옵션을 비활성화 합니다.
bUseUnityBuild = false;
ExtraModuleNames.Add("funapi_plugin_ue4");
}
39.15.4.2. 버전 4.22 이전: iPhone XS 에서 크래시하는 문제¶
외부 라이브러리를 포함하여 언리얼의 사용자 코드에서 Standard Library 의 동적 메모리 할당자를 사용하는 경우 iPhone XS 에서 아래와 같은 메시지를 출력하면서 크래시하는 증상이 빈번하게 발생합니다.
Attempting to free a pointer we didn't allocate!
이 문제는 언리얼 이슈 링크 에 등록되어 수정될 예정이며, 문제가 해결된 버전이 나오기 전까지는 번거롭더라도 iOS 플랫폼 타겟 빌드를 생성 할 때에는 언리얼 엔진 소스코드를 수정하는 것이 가장 간단한 해결방법입니다.
언리얼 엔진 소스를 github 에서 다운 받습니다. 언리얼 엔진 소스를 내려받는 방법은 링크 를 참고 해 주세요.
{unreal_root_directory}/Engine/Source/Runtime/Core/Private/Apple/ApplePlatformMemory.cpp 파일 내용 중 FMalloc* FApplePlatformMemory::BaseAllocator() 함수의 내용을 아래와 같이 수정 해 주세요.
// 모든 경우에 EMemoryAllocatorToUse::Ansi를 사용하도록 추가.
AllocatorToUse = EMemoryAllocatorToUse::Ansi;
// 기존 코드는 주석처리
//if (FORCE_ANSI_ALLOCATOR)
//{
// AllocatorToUse = EMemoryAllocatorToUse::Ansi;
//}
//else if (USE_MALLOC_BINNED2)
//{
// AllocatorToUse = EMemoryAllocatorToUse::Binned2;
//}
//else
//{
// AllocatorToUse = EMemoryAllocatorToUse::Binned;
//}
언리얼 엔진을 소스빌드 후에 실행해서, iOS 패키징을 하려는 프로젝트를 열어주세요.
언리얼 엔진을 소스빌드하는 방법은 아래 링크를 참고해주세요. https://api.unrealengine.com/KOR/Programming/Development/BuildingUnrealEngine/index.html
39.16. Cocos2d-x plugins¶
Download the Cocos2d-x client plugin from GitHub. If you need a version that includes encryption, you need to request it from iFun Engine support.
39.17. Bug reports¶
Email iFun Engine support with bug reports or comments about client plugins.
39.13. Social network plugins¶
You can use Funapi Social plugins to easily login and post with accounts on Facebook, Twitter, etc. The installation package is in the
additional-plugins/Packages
folder.Scripts
is the folder to extract files added during package import. If the package has been installed, there is no need to copy files from theScripts
folder. The sample source is installed in theTester
folder.Tip
Social network plugins are currently only available in the Unity version.
39.13.1. Interface classes¶
Shared interface classes are used in the Funapi Social Plugin. A class called SocialNetwork has the following interfaces.
You can use the FacebookConnector class and TwitterConnector class made by inheriting this SocialNetwork class.
Note
These social apps must be made before plugin connection. Make the Facebook app for Facebook, and the Twitter app for Twitter. You need to install Facebook SDK for Unity as well before installing the facebook-plugin we have provided for Facebook.
39.13.2. Facebook¶
Plugin functions
The Facebook SDK feature is wrapped and only a few functions are provided. You can request functions you wish to use beyond those provided below from iFun Engine support.
Log in with a Facebook account
Request data and profile photo
Request friends list and profile photo
FacebookConnector is a MonoBehaviour object and thus must be preregistered on the scene. The following code imports registered FacebookConnector objects to reset them.
Facebook login
There are two types of login function. To login with only read permissions, use
LogInWithRead
. To login with write permissions as well, you need to login withLogInWithPublish
.A list of permissions needs to be sent as a login function parameter. These permissions can be viewed on the Facebook app’s Status & Review page. Several functions are provided by default, and most functions, including posting, can be requested in the app after authentication from Facebook. To test in Unity Editor, you can get an access token for testing from Graph API Explorer.
Photos are automatically requested as well when requesting a friends list, but if not all friends’ photos are needed right away, you have the option of whether or not to download them.
If you request photos, they are downloaded asynchronously and sequentially. When the profile photo download is finished, the PictureCallback event function is invoked. If you want to receive a notification when photo download is finished, you need to register a callback function in PictureCallback events when initializing FacebookConnector objects.
39.13.3. Twitter¶
Plugin functions
The Twitter plugin provides the following features. If there are other features you want to use, you can request them from iFun Engine support.
Log in with a Twitter account
Request data and profile photo
Request friends list and profile photo
Upload text to your Twitter feed
TwitterConnector is a MonoBehaviour object and thus must be preregistered on the scene. The following code imports registered TwitterConnector objects to reset them.
When Twitter is initialized,
Consumer Key
andConsumer Secret
must be transmission parameters in order to call theInit()
function. You can view these values in the Twitter app [Keys and Access Tokens] tab.Twitter login
Logging into Twitter is relatively complicated, but once you’ve logged in the
Access Token
received when first authenticating the app stays in use so that you only need to deal with this complicated process once.The
Access Token
obtained by invoking theRequestAccess()
function can be used for permanent authentication, so with this value alone, you can post to Twitter at any time. After logging in once, this value is encrypted and saved by the plugin , then confirmed the next timeInit()
is invoked. Login is notified through the callback function with no other authentication process.Once logged in, you can post tweets. Sent messages are posted to your feed as follows: