📜 ⬆️ ⬇️

Unity (Unet) - integration with social networks and secure WebSockets

Introduction


In this article I want to share my experience of integrating a network application (in my case, games) with social networks. Since I try, if possible, not to resort to third-party solutions, the network part was developed on what Unity3D offers, namely Unet using its low-level part ( LLAPI ). NetworkClient on the client connects to the NetworkServer on the remote server. The game server is also written in Unity3D . Work in such a bundle, although it requires in-depth knowledge of Unet , but has its undeniable advantages.

Problem


And now it is time to place the application in the social. network. Connecting API social. network, set up the application in the social. network, compiled WebGl assembly and laid out the game on the server. The first launch of the application immediately revealed an error: the WebGl assembly cannot open a normal Socket and requires working only through WebSocket . Everything is clear here - this is my omission, because I did an extreme application under the Unity Web Player , which worked on ordinary Socket 'ah. Having decided to set up a network for working with WebSocket 's (more on this later), I ran into the following problem. Since soc. the network (at least the one with which I integrated) works over a secure protocol ( Https ) and makes demands on its content, as well as work through a secure protocol. “It doesn't matter” - I thought, we’ll do it now ... A few days of “digging” the Internet, communicating with the Unity developers plunged me into despondency: there is no support for secure WebSocket 's ( WSS ) in Unet and it is not known when it will be. As I understand it, most developers in this place go to the "photon". But we are not like that!

So what's the matter soc. network. For the usual web-build, we work with the usual WebSocket 's and enjoy life.

Common scripts


Client (c #):
#if _CLIENT_ using UnityEngine; using UnityEngine.Networking; namespace EXAMPLE.CLIENT { public class NetworkClientExample : NetworkClient { NetworkWriter mWriter = new NetworkWriter(); public NetworkClientExample() : base() { Configure(NetworkableConfig.GetConnectionConfig(), 1); RegisterHandler(MsgType.Connect, OnConnect); RegisterHandler(MsgType.Disconnect, OnDisconnect); RegisterHandler(MsgType.Error, OnError); RegisterHandler((short)EnumRpc.RpcHelloWorld, RpcLobbbyHelloWorld); } public void OnConnect(NetworkMessage _msg) { Debug.Log("OnConnectLobby"); mWriter.StartMessage((short)EnumRpc.CmdHelloWorld); mWriter.Write("Hello from client"); mWriter.FinishMessage(); SendWriter(mWriter, Channels.DefaultReliable); } void OnDisconnect(NetworkMessage _msg) { Debug.Log("OnDisconnectLobby"); UnregisterHandler(MsgType.Connect); UnregisterHandler(MsgType.Disconnect); UnregisterHandler(MsgType.Error); UnregisterHandler((short)EnumRpc.RpcHelloWorld); } void OnError(NetworkMessage _msg) { Debug.Log("OnErrorLobby"); } void RpcLobbbyHelloWorld(NetworkMessage _msg) { Debug.Log(_msg.reader.ReadString()); } } // class } // namespace #endif 


Using NetworkClientExample (c #):
 NetworkClientExample client = new NetworkClientExample(); client.Connect(NetworkableConfig.ServerHost, NetworkableConfig.ServerPort); 


')
Server connection to client (c #):
 #if _SERVER_ using UnityEngine; using UnityEngine.Networking; namespace EXAMPLE.SERVER { public class ConnectionServerExample: NetworkConnection { NetworkWriter mWriter = new NetworkWriter(); public void SendHelloWorld() { mWriter.StartMessage((short)EnumRpc.RpcHelloWorld); mWriter.Write("Hello from server"); mWriter.FinishMessage(); SendWriter(mWriter, Channels.DefaultReliable); } } // class } // namespace #endif 


Server (c #):
 #if _SERVER_ using UnityEngine; using UnityEngine.Networking; namespace EXAMPLE.SERVER { public class NetworkServerExample : MonoBehaviour { public const int sMaxConnections = 5000; void Start() { NetworkServer.SetNetworkConnectionClass<ConnectionServerExample>(); ConnectionConfig config = NetworkableConfig.GetConnectionConfig(); NetworkServer.Configure(config, sMaxConnections); NetworkServer.RegisterHandler(MsgType.Connect, OnConnect); NetworkServer.RegisterHandler(MsgType.Disconnect, OnDisconnect); NetworkServer.RegisterHandler(MsgType.Error, OnError); NetworkServer.RegisterHandler((short)EnumRpc.CmdHelloWorld, CmdHelloWorld); NetworkServer.Listen(NetworkableConfig.ServerPort ); } void OnConnect(NetworkMessage _msg) { Debug.Log("OnConnect"); } void OnDisconnect(NetworkMessage _msg) { Debug.Log("OnDisconnect"); } public void OnError(NetworkMessage _msg) { Debug.Log("OnError"); } void CmdHelloWorld(NetworkMessage _msg) { Debug.Log(_msg.reader.ReadString()); ((ConnectionServerExample)_msg.conn).SendHelloWorld(); } } // class } // namespace #endif 


Network constants (c #):
 using UnityEngine.Networking; namespace EXAMPLE { public enum EnumRpc : short { RpcHelloWorld = 1, CmdHelloWorld, } public static class NetworkableConfig { /// <summary> ///  Lobby- /// </summary> public const int ServerPort = 5000; /// <summary> ///  Lobby-web- /// </summary> public const int ServerPortWebSocket = 5500; /// <summary> ///  Lobby- /// </summary> public const string ServerHost = "127.0.0.1"; /// <summary> /// Timeout  /// </summary> public const int DisconnectTimeout = 10000; /// <summary> ///     /// </summary> public static ConnectionConfig GetConnectionConfig() { ConnectionConfig config = new ConnectionConfig(); config.AddChannel(QosType.ReliableSequenced); config.AddChannel(QosType.Unreliable); config.DisconnectTimeout = DisconnectTimeout; return config; } } // class } // namespace 


Moving to WebSockets


In my case, server-side game logic should be common for both “mobile” and “browser-based” clients, so I decided to switch to WebSocket 's. But after reading about the "stagnation" of the latter, it was decided to make two parallel servers. While thinking about the interaction of these servers, I stumbled across one forum at the mention of the NetworkServerSimple class and rejoiced. We will set it on WebSocket 's. And we will need only to transfer its clients to the main server.

WebSocket Server (c #):
 #if _SERVER_ using UnityEngine.Networking; namespace EXAMPLE.SERVER { class NetworkServerWebSocket : NetworkServerSimple { public void Start(ConnectionConfig _config, int _maxConnections) { base.Initialize(); base.Configure(_config, _maxConnections); //    webSocket' useWebSockets = true; Listen(NetworkableConfig.ServerPortWebSocket); } public override void OnConnected(NetworkConnection _conn) { base.OnConnected(_conn); //         NetworkServer.AddExternalConnection(_conn); } public override void OnDisconnected(NetworkConnection _conn) { //        NetworkServer.RemoveExternalConnection(_conn.connectionId); base.OnDisconnected(_conn); } } // class } // namespace #endif 


In order to start the WebSocket -server, you need to edit the script of our main server:

Fixes in NetworkServerExample (c #):
 public class NetworkServerExample : MonoBehaviour { //... NetworkServerWebSocket mServerWebSocket; //... void Start() { //... mServerWebSocket = new NetworkServerWebSocket(); mServerWebSocket.Start(config, sMaxConnections); //... } //... void Update() { //... // WebSocket      if(mServerWebSocket != null) { mServerWebSocket.Update(); } //... } //... } 


And it all worked right away ... but not for long. Until connections to both servers at the same time went. It turned out that Unet is unable to solve the simple problem of uniqueness of connection identifiers of all working servers. It's a shame, annoying, but ... where without crutches. We will simply introduce our class for the WebSocket connection, which will replace its identifier by shifting to the maximum possible number of connections of the main server. Since the server is remote and, often, dedicated - we can close our eyes to a small waste of memory to allocate a double array of links to connections. So:

WebSocket server (c #) server class:
 #if _SERVER_ using UnityEngine.Networking; namespace EXAMPLE.SERVER { public class ConnectionLobbyServerWebSocket : NetworkConnection { public override void Initialize(string networkAddress, int networkHostId, int networkConnectionId, HostTopology hostTopology) { base.Initialize(networkAddress, networkHostId, networkConnectionId + NetworkServerExample.sMaxConnections, hostTopology); } public override bool TransportSend(byte[] bytes, int numBytes, int channelId, out byte error) { return NetworkTransport.Send(hostId, connectionId - NetworkServerExample.sMaxConnections, channelId, bytes, numBytes, out error); } } // class } // namespace #endif 


Now everything is fine!

Secure WebSockets


Since I am a complete layman in server administration - for me this item turned out to be the most difficult. For hosting I use Windows Server . And the more I immersed in the topic of forwarding from WSS to WS for IIS , the worse it became. Surely for “Atz-Odmin” this is a trivial task, but I am not him. However, during the study
IIS, I realized that on Apache is very easy to do. Hooray! I know the word apache . Surely the aforementioned admin will be bad, from a combination of Windows Server + Apache , well, okay.
In order to forward in Apache from WSS to WS, it is necessary to support SSL on it (there is a lot of information from real specialists on this), enable the “mod_proxy.so” and “mod_proxy_wstunnel.so” modules and configure the forwarding.

httpd.conf
 #  c   LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so #      ProxyPass /my_proxy/ ws://localhost:5500/ ProxyPassReverse /my_proxy/ ws://localhost:5500/ 

Here “my_proxy” is any our identifier that will be used to connect to the client. 5500 - the port that our WebSocket server listens on

WebSocket- server is already listening to the desired port, it remains to configure the client connection.

Fix NetworkClientExample (c #)
 //... public NetworkClientExample() : base() { //... //      . _VK_ -    .  #if _VK_ // -  NetworkServer NetworkServer.useWebSockets = true; #endif //... } //... 


Using NetworkClientExample (c #)
 NetworkClientExample client = new NetworkClientExample(); #if _VK_ client.Connect("_/my_proxy/", 443); #else client.Connect(NetworkableConfig.ServerHost, NetworkableConfig.ServerPort); #endif 

your_domain is the name of your domain without a protocol, for example: habrahabr.ru
my_proxy is a proxy identifier that we designated in “httpd.conf”
Do not forget the "/" after the proxy id!

There are very few. Now you need to convert Unet requests through regular web sockets ( WS ) to secure WSS web sockets. This can be done using a small JavaScript- script, either by inserting it directly into the body of your web-page, or by connecting it as a plugin to the project. I was more comfortable with the second option. Therefore, in the “Assets / Plugins” folder of our project, we create a script, for example, “WssHack.jspre” (with the extension “.jspre”, otherwise, “yunka” can play a jerk) and the following contents:

WssHack.jspre (javascript)
 Object.defineProperty(Module, "asmLibraryArg", { set: function (value) { value._JS_UNETWebSockets_SocketCreate = function (hostId, urlPtr) { var url = Pointer_stringify(urlPtr).replace(/^ws:\/\//, "wss://"); urlPtr = Runtime.stackAlloc((url.length << 2) + 1); stringToUTF8(url, urlPtr, (url.length << 2) + 1); return _JS_UNETWebSockets_SocketCreate(hostId, urlPtr); }; Module._asmLibraryArg = value; }, get: function () { return Module._asmLibraryArg; }, }); 


I hope everything will work for you! Ask any questions, feel free to.

Source: https://habr.com/ru/post/352886/


All Articles