SFS2X Docs / ExamplesUnity / connector
» Connector
» Overview
The Connector example shows how to setup and use the SmartFox client API object, establish a connection to the server and login. It also shows how to deal with the different requirements of the Unity build targets, making use of a few conditional compilation statements.
The example also features the logic needed to activate the protocol cryptography that was introduced in SmartFoxServer 2X starting from version 2.10. This will be discussed at the bottom of this tutorial.
The Unity project consists of a single scene with a canvas containing two game objects (called "panels") representing the initial login view and a generic main view acting as a placeholder for what comes after a successful login. On the login view the user can enter their name and hit the button to connect to SmartFoxServer and execute the login process.
The example features a single script component. A number of properties exposed in the Editor's Inspector panel allow configuring the connection parameters and API logging behavior.
>> DOWNLOAD the source files <<
» Setup & run
In order to setup and run the example, follow these steps:
- unzip the examples package;
- launch the Unity Hub, click on the Open button and navigate to the Connector folder;
- if prompted, select the Unity Editor version to use (v2021.3 or later is recommended);
- click on the SmartFoxServer → Demo Project Setup menu item in the Unity UI, and follow the additional instructions provided in the appeared Editor window.
The client's C# code is in the Unity project's /Assets/Scripts folder, while the SmartFoxServer 2X client API DLLs are in the /Assets/Plugins folder. Read the introduction to understand why multiple DLLs are used.
» Introduction to code
The code for this example is contained in the script file called Controller in the /Assets/Scripts folder, attached to the empty Controller game object in the scene.
The file is a basic Unity C# script implementing the Start() and Update() methods. Additionally, there's a few listeners for the events fired by UI components (buttons, input), some helper methods and the listeners for SmartFoxServer's client API events.
» Fields declaration
At the top of the Controller script, where fields are declared, other than the links to UI components the script needs to interact with, there's a number of public properties to configure the connection to SmartFoxServer. These settings are exposed in the Inspector panel for the Controller game object.
The settings are:
- Host, Tcp Port and Http Port are the basic settings required to establish a connection with SmartFoxServer. The reason why two port settings are available will be clear when describing the connection step.
- Https Port and Encrypt settings are related to protocol encryption, which will be discussed in a dedicated paragraph below.
- Use Http Tunnel allows enabling SmartFoxServer's BlueBox, the HTTP tunneling solution briefly discussed in a separate paragraph below.
- Zone contains the name of an existing Zone on the server. By default most examples use the BasicExamples Zone which is available by default after a fresh installation of SmartFoxServer.
- Debug and Log Level configure the behavior of the API's internal logger.
Of course all port values must match che corresponding settings in the server configuration (see AdminTool's Server Configurator module).
Back to the fields declaration in the script, the sfs property keeps a reference to the entry point of the SmartFoxServer C# client API, the SmartFox class. This is the main class used by the client to interact with the server through a set of requests and responses (under the form of events).
» Unity's Start callback
The Start() method just takes care of the interface initialization: the login view is displayed and focus is set onto the username input it contains.
The method also makes sure that the example will always run in background. Although this is not particularly important in this case, in the next examples this will be useful for testing, when two clients are often opened at the same time on the same computer to test their interactions.
private void Start() { // Make sure the application runs in background Application.runInBackground = true; // Init view loginPanel.SetActive(true); mainPanel.SetActive(false); // Focus on username input nameInput.Select(); nameInput.ActivateInputField(); }
» Establishing a connection
Other than the username input field, the UI features a single button used to start the connection & login process. Technically these are two separate steps, but they will be executed back-to-back when the user clicks on the Login button or hits the enter key after typing their username. The two events are handled by the OnLoginButtonClick() and OnNameInputEndEdit() listeners respectively, both calling the Connect() method.
Please note that in a real-case scenario, the login view should also contain a password input field and credentials should be validated on the server side. For sake of simplicity, all examples described in this and other tutorials will feature guest login, allowing users to access the application or game by entering just a name. If the input field is left empty, a name is auto-generated by the server and assigned to the user.
The first relevant code portion of the Connect() method takes care of creating the SmartFox class instance and registering the event handlers required by the example.
The code contains a few conditional compilation instructions to instantiate the main API object in a slightly different way depending on the type of connection we want to establish with SmartFoxServer:
- a TCP socket connection, which is the default one in SmartFoxServer and it is used by all Unity builds except WebGL;
- a WebSocket connection, the only one supported by Unity WebGL builds and provided by SmartFoxServer's internal web server (which, of course, must be active!).
For more information on conditional compilation, please visit this page in the Unity documentation.
// Initialize SmartFox client #if !UNITY_WEBGL sfs = new SmartFox(); #else sfs = new SmartFox(encrypt ? UseWebSocket.WSS_BIN : UseWebSocket.WS_BIN); #endif
The class constructor used for WebGL build requires to specify which type of WebSocket connection should be established, if secure or normal. This will be discussed when describing protocol encryption in the last part of this tutorial.
If you are a bit familiar with Unity then the expression "event handler" should not sound new to you. In fact many of the built-in callback methods in Unity use events to handle situations that will occur at an undefined moment in the future. For example when two objects collide in your game, you have to implement the OnCollisionEnter event handler to have your code react to that particular collision event.
SmartFoxServer uses events quite a lot because you can receive messages at any time from the server or from other users. All you have to do is write the appropriate functions to manage the different situations that may occur.
// Add event listeners sfs.AddEventListener(SFSEvent.CONNECTION, OnConnection); sfs.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost); sfs.AddEventListener(SFSEvent.CRYPTO_INIT, OnCryptoInit); sfs.AddEventListener(SFSEvent.LOGIN, OnLogin); sfs.AddEventListener(SFSEvent.LOGIN_ERROR, OnLoginError);
We'll analyze the above event handlers while progressing in this tutorial, and more advanced handlers in other tutorials.
The next code portion configures the API internal logger, used to debug the messages exchanged between the client and the server and report other internal informations. We want the messages of a given severity level and up (set in the Inspector panel) to be displayed in the Unity Editor's Console panel. Here a few special listeners could also be added by means of the SmartFox.AddLogListener() method: they offer better control on how and where to display debug messages (for example an in-game debug panel).
// Configure internal SFS2X logger sfs.Logger.EnableConsoleTrace = true; sfs.Logger.LoggingLevel = logLevel;
The four log levels available in SmartFoxServer are:
- DEBUG — verbose, low-level API debug information; we recommend this setting only for protocol level debugging.
- INFO — standard internal API debug informations; for example this level shows logs the binary content of the messages exchanged between the client and the server. This is the default level to which the Logger is set and can be useful to see the messages a game sends to and receives from SmartFoxServer.
- WARN and ERROR — internal API warnings and errors; they can be useful to know if you are doing something wrong with the API in your game.
In the last part of the connection code, the configuration parameters required to establish a connection are set using the ConfigData object, which is then passed to the SmartFox.Connect() method, to actually start the connection process.
// Set connection parameters ConfigData cfg = new ConfigData(); cfg.Host = host; cfg.Port = tcpPort; cfg.Zone = zone; cfg.HttpPort = httpPort; cfg.HttpsPort = httpsPort; cfg.BlueBox.IsActive = useHttpTunnel; cfg.BlueBox.UseHttps = encrypt; cfg.Debug = debug; #if UNITY_WEBGL cfg.Port = encrypt ? httpsPort : httpPort; #endif // Connect to SmartFoxServer sfs.Connect(cfg);
The code collects the settings provided in the Inspector panel before and starts the connection by calling the SmartFox.Connect() method. The mandatory parameters are the server's domain name or IP address (Host) and the primary connection port (Port) for TCP socket connection. Please note that, in case of WebGL build, a conditional compilation statement overwrites the port setting with the value required by WebSocket connections (either secure or normal). In fact WebSocket connection uses the SmartFoxServer's internal web server HTTP/S port.
As it regards the other settings instead:
- The Zone is where the user will be logged in after the successful connection.
- Other than for WebSocket communication, the HttpPort and HttpsPort settings are used by the HTTP tunneling system (BlueBox) and for protocol encryption initialization.
- The BlueBox-related settings will be discussed in the paragraph dedicated to HTTP tunneling below.
- If the Debug parameter is set to true, the internal logger of the SmartFox API is enabled and debug messages are displayed in Unity's Console panel and possibly passed to log event listeners as discussed before.
» Handling basic SmartFoxServer events
In this example we register the handlers (also called listeners) for the most basic events fired by the API, those related to connection, login and disconnection. In practice, when the client gets connected to the server, the OnConnection() method in the Unity script component is executed. Then the login is attempted and the OnLogin() or OnLoginError() methods are called. Eventually, when the client gets disconnected (for whatever reason), the OnConnectionLost() method is called. The last listener, OnCryptoInit(), will be discussed in the paragraph about protocol encryption.
Before discussing the event handlers in detail, a fundamental note on thread safety: Unity implements thread safety by restricting the access to its own API to the main thread only. To avoid blocking the main thread during network communication, the SmartFox API rely on its own threads. In order to access the Unity API (for example to update the game objects on the scene according to an event), the network event handlers can't be called directly by the SmartFox instance: events must be queued internally and Unity's main thread needs to "poll" the queue on a regular basis to process them.
This behavior is controlled by the SmartFox.ThreadSafeMode property: if set to true (default in Unity), the events are queued and the handlers are called when the SmartFox.ProcessEvents() method is executed only. This is done in Unity's Update() callback.
private void Update() { // Process the SmartFox events queue if (sfs != null) sfs.ProcessEvents(); }
All event callback methods have the same method signature with a single parameter of type BaseEvent. It contains an Hashtable of event-specific parameters that developers can access. Which parameters are provided by what events is documented in the SFSEvent API documentation.
» CONNECTION event handler
The OnConnection() method is called when the API completes the connection process (whether successfully or not) and the CONNECTION event is dispatched. The event parameter is a "success" bool.
private void OnConnection(BaseEvent evt) { // Check if the connection was established or not if ((bool)evt.Params["success"]) { Debug.Log("Connection established successfully"); Debug.Log("SFS2X API version: " + sfs.Version); Debug.Log("Connection mode is: " + sfs.ConnectionMode); #if !UNITY_WEBGL if (encrypt) { Debug.Log("Initializing encryption..."); // Initialize encryption sfs.InitCrypto(); } else { // Attempt login Login(); } #else // Attempt login Login(); #endif } else { // Show error message errorText.text = "Connection failed; is the server running at all?"; // Enable user interface EnableUI(true); } }
If the connection is successful, other than printing some details in the Console panel (note that a WebGL build prints a different "connection mode" from the other Unity builds), the login is attempted by calling the Login() method. The listener is quite articulated in order to handle protocol encryption, which is initialized here. We will discuss it in the last part of this tutorial: by now you should only assimilate that upon successful connection, the login should be immediately attempted.
In case of connection error instead, for example due to the server being unreachable, an error message is displayed in the login view.
The Login() method is in charge of sending the LOGIN request to SmartFoxServer. The code is simple and follows the standard approach to send any type of request to the server via the SmartFox.Send() method. Except for some special cases (connection, disconnection), a client sends all its requests in this way — whether they are build-in requests (like the login one) or Extension requests (to exchange custom data with the server).
private void Login() { Debug.Log("Performing login..."); // Login sfs.Send(new LoginRequest(nameInput.text)); }
As mentioned before, in a real-case scenario a password should be sent together with the username. Also, as the Zone name is not directly passed to the LoginRequest instance, the API uses the value set in the ConfigData object (created before attempting the connection).
» LOGIN event handler
The OnLogin() method is called when a successful login occurs and the LOGIN event is notified. In this example the successful login simply switches the login and main views. As already mentioned, the main view is only a placeholder for the actual game interface. In this example it shows a Logout button to disconnect from the server and go back to the login view.
private void OnLogin(BaseEvent evt) { Debug.Log("Login successful"); // Update view SwitchView(); }
When the Logout button is clicked, its listener calls the SmartFox.Disconnect() method to execute a manual disconnection from the server.
» LOGIN ERROR event handler
In case an error occurs, the LOGIN_ERROR is fired and the OnLoginError() method is called. Some of the possible errors are: invalid Zone name, invalid username, full Zone (no more users can join it), etc. The event parameter contains a message detailing the error message, which can be displayed on the login view as shown below.
private void OnLoginError(BaseEvent evt) { Debug.Log("Login failed"); // Disconnect // NOTE: this causes a CONNECTION_LOST event with reason "manual", which in turn removes all SFS listeners sfs.Disconnect(); // Show error message errorText.text = "Login failed due to the following error:\n" + (string)evt.Params["errorMessage"]; // Enable user interface EnableUI(true); }
It is important to note that, if a login error occurs, a disconnection should always be forced. In fact the client is still connected, even if the user couldn't complete the login. The disconnection causes a client reset, so a new connection and login can be attempted.
» CONNECTION LOST event handler
The OnConnectionLost() method is called when the client loses the connection, which is notified through the CONNECTION_LOST event. The reason is contained in a string in the event's parameters.
private void OnConnectionLost(BaseEvent evt) { // Remove SFS listeners sfs.RemoveEventListener(SFSEvent.CONNECTION, OnConnection); sfs.RemoveEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost); sfs.RemoveEventListener(SFSEvent.CRYPTO_INIT, OnCryptoInit); sfs.RemoveEventListener(SFSEvent.LOGIN, OnLogin); sfs.RemoveEventListener(SFSEvent.LOGIN_ERROR, OnLoginError); sfs = null; // Show login view loginPanel.SetActive(true); mainPanel.SetActive(false); // Show error message string reason = (string)evt.Params["reason"]; Debug.Log("Connection to SmartFoxServer lost; reason is: " + reason); if (reason != ClientDisconnectionReason.MANUAL) { // Show error message string connLostMsg = "An unexpected disconnection occurred; "; if (reason == ClientDisconnectionReason.IDLE) connLostMsg += "you have been idle for too much time"; else if (reason == ClientDisconnectionReason.KICK) connLostMsg += "you have been kicked"; else if (reason == ClientDisconnectionReason.BAN) connLostMsg += "you have been banned"; else connLostMsg += "reason is unknown."; errorText.text = connLostMsg; } // Enable user interface EnableUI(true); }
In this example a disconnection occurs if the user clicks on the Logout button on the main view, if the user idle time configured on the server is exceeded (the number of seconds passed since the last request from the client was received) or if the physical connection with the server is interrupted (for example stopping SmartFoxServer). Other disconnection reasons include the user being kicked or banned by a moderator or administrator.
When the disconnection occurs (for any reason): all the listeners added to the SmartFox instance are removed and the sfs property is set to null; the main view is hidden and the login view is displayed; unless the disconnection was manually requested (the user clicked the Logout button in the main view), a warning message is displayed in the login view.
This returns the example to the initial state, and the user can start another connection. It is recommended not to reuse the SmartFox instance after a connection is lost: every connection should be used for a single session; thus we have to remove the listeners from the old one, so it can be garbage-collected.
» Unity's OnApplicationQuit callback
Unity calls the OnApplicationQuit() method when the application quits (of course) and, in the Editor, when playmode is stopped. It is important to use this method to check if the connection with SmartFoxServer is currently active and, in case, execute a disconnection.
private void OnApplicationQuit() { if (sfs != null && sfs.IsConnected) sfs.Disconnect(); }
The reason why a disconnection is recommended on application quit is that an active network socket during the quit process can cause a crash on some platforms.
Additionally, when inside the Unity Editor, stopping playmode doesn't interrupt the internal threads launched by the SmartFox API (or any other script, by the way), which may lead to unwanted issues. Forcing a disconnection makes sure all threads are stopped as appropriate.
» HTTP tunneling
The BlueBox is a technology embedded in SmartFoxServer that allows clients to connect from behind firewalls and proxies. Behind the scenes, the BlueBox uses an HTTP tunnel to wrap the SmartFoxServer protocol into HTTP requests, which in turn allows to pass through firewalls and most proxies which prevent a regular TCP socket connection from being established. More information on the BlueBox is available in this document.
The BlueBox operation is mostly transparent: you just need to configure it by means of the ConfigData.BlueBox object, as shown before:
// Set connection parameters ConfigData cfg = new ConfigData(); ... cfg.BlueBox.IsActive = useHttpTunnel; cfg.BlueBox.UseHttps = encrypt; ...
The two settings above respectively activate the system and instruct it to use the HTTPS port instead of the HTTP port in case communication must be encrypted (see next paragraph). Everything else works exactly like in a regular TCP socket connection, with the same events discussed before being dispatched.
» Protocol encryption
The encryption of messages exchanged between SmartFoxServer and its client is achieved in two different ways depending on the connection type:
- Regular TCP socket connection (used by all Unity builds except WebGL) requires an initial HTTPS negotiation which allows the client to acquire the private key later used for 128 bit symmetric encryption of the communication.
- WebSocket connection (used by Unity WebGL build) doesn't require a special handshake; providing a couple of dedicated parameters at connection time is everything you need.
IMPORTANT
Protocol encryption requires a good understanding of the approach adopted in the API and a specific setup of SmartFoxServer 2X. For this reason we strongly recommend to read this document carefully before activating it.
Also, encryption can't be tested on localhost (where you are probably running this example) because a valid SSL certificate is needed.
Enabling protocol encryption on the client side requires checking the Encrypt checkbox in the Unity Editor's Inspector panel for the Controller game object. Together with the conditional compilation for WebGL build, this determines the code behavior.
» Regular TCP socket connection (non-WebGL builds)
In order to activate protocol encryption when a regular TCP socket connection is used, a special cryptography initialization must be performed between the connection and the login steps. Let's go back to the OnConnection() listener (the irrelevant code has been hidden).
private void OnConnection(BaseEvent evt) { // Check if the connection was established or not if ((bool)evt.Params["success"]) { #if !UNITY_WEBGL if (encrypt) { Debug.Log("Initializing encryption..."); // Initialize encryption sfs.InitCrypto(); } else { // Attempt login Login(); } #else // Attempt login ... #endif } else { ... } }
Upon successful connection to the server, the SmartFox.InitCrypto() method must be called: this executes the initial HTTPS negotiation with the server to get the encryption key, then it always fires the CRYPTO_INIT event.
The event is handled by the OnCryptoInit() listener which checks the success of the initialization.
private void OnCryptoInit(BaseEvent evt) { if ((bool)evt.Params["success"]) { Debug.Log("Encryption initialized successfully"); // Attempt login Login(); } else { Debug.Log("Encryption initialization failed: " + (string)evt.Params["errorMessage"]); // Disconnect // NOTE: this causes a CONNECTION_LOST event with reason "manual", which in turn removes all SFS listeners sfs.Disconnect(); // Show error message errorText.text = "Encryption initialization failed"; // Enable user interface EnableUI(true); } }
If the client could connect to the server via HTTPS and retrieve the encryption key, then the "success" parameter is true: we can now proceed with the login as described before. All messages exchanged by the client and SmartFoxServer will be encrypted.
In case an error occurs during the initialization, other than showing a warning on the login view, a disconnection should be forced. In fact the client is still connected, even if the protocol encryption initialization couldn't be completed. The disconnection causes the client to be properly reset to the initial state.
» WebSocket connection (WebGL build only)
In order to establish an encrypted WebSocket connection — aka WebSocketSecure (WSS) — we have to pass the proper flag to the SmartFox constructor.
// Initialize SmartFox client #if !UNITY_WEBGL ... #else sfs = new SmartFox(encrypt ? UseWebSocket.WSS_BIN : UseWebSocket.WS_BIN); #endif
The WSS_BIN flag instructs the API to use the WSS protocol. This also needs the primary port to be set accordingly. This is why the port setting is overridden with the proper value when the ConfigData object is created.
// Set connection parameters ConfigData cfg = new ConfigData(); cfg.Host = host; cfg.Port = tcpPort; ... #if UNITY_WEBGL cfg.Port = encrypt ? httpsPort : httpPort; #endif // Connect to SmartFoxServer sfs.Connect(cfg);
When the connection is established successfully, the Login() method can be called directly by the OnConnection() listener. The connection is in fact already encrypted, and it doesn't require additional steps like described above.
private void OnConnection(BaseEvent evt) { // Check if the connection was established or not if ((bool)evt.Params["success"]) { #if !UNITY_WEBGL ... #else // Attempt login Login(); #endif } else { ... } }
You can now proceed to the next example in this Unity series to learn new features of SmartFoxServer.
» More resources
You can learn more about the SmartFoxServer basics by consulting the following resources:
- Development basics (introduction to the Zone concept and basic client-server communication steps)
- The connection phase
- The login phase
- How to create an Extension-based custom login
- Protocol cryptography