Home .NET The simplest implementation of cross-platform multiplayer on the example of the evolution of a single .NET game. Part 2, more technical

The simplest implementation of cross-platform multiplayer on the example of the evolution of a single .NET game. Part 2, more technical

by admin

So, the promised sequel to my first article from the sandbox. , which will include a bit of technical details on implementing a simple multiplayer game with the ability to play from clients on different platforms.
I finished the previous part by saying that in the latest version of my game "Magic Yatzy" I’m using WebSockets as a client-server interaction tool. Now some technical details.

1. General

In general, everything looks as shown in this diagram :
The simplest implementation of cross-platform multiplayer on the example of the evolution of a single .NET game. Part 2, more technical
Above is a schematic representation of the interaction between the three clients on different platforms and the server. Let’s take a closer look at each part.

2. Server

I have a server based on MVC4 running as a "Cloud service" in Windows Azure. Why this choice. It’s simple :
1) I don’t know anything other than .NET.
2) I only have WebSocket for game-related interactions, everything else like checking server status, getting/saving points and stuff is through WebApi – so MVC.
3) I have a subscription to Azure services.
According to the diagram above – the server consists of three parts :
1) ServerGame- implementation of all game logic;
2) ServerClient- a kind of intermediary between the game and the network part;
3) WSCommunicator- the part responsible for networking with the client – receiving/sending commands.
Concrete realization ServerGame and ServerClient depends on the particular game you are developing. In general ServerClient receives a command from the client, processes it and notifies the game of the client’s action. At the same time, it keeps track of changes in the game state ( ServerGame ) and notifies (sends information via the WSCommunicator ) of its client about any changes.
For example, regarding my dice game : on his turn, a user on the Windows 8 client fixed some dice (made it so that their value did not change on his next roll). This information was transmitted to the server and ServerClient notified the ServerGame which made the necessary changes in the state of the game. This change was notified to all other ServerClient ‘s connected to this game (in this case, WP and Android), and they in turn sent the information to the devices to notify users via the UI.
It should be said that in the class itself. ServerGame has nothing "server-like" about it. This is a normal .NET class that has a common interface with ClientGame So we can substitute it instead of ClientGame in the client program and thus get a local game. That’s how local game works in my "kniffel" – when from one UI page both local and network game is possible.
WSCommunicator – is, as I said, the class in charge of networking. This one in particular implements it with WebSockets. In .NET 4.5 we have our own implementation of WebSockets. The core of this implementation is the WebSocket class, WSCommunicator is essentially a wrapper over it, which implements opening/closing a connection, trying to reconnect, and sending/receiving data in a particular format.
Now a bit of code. The Http Handler is used for the initial connection. You don’t have to add a physical page. It’s enough to set parameters in WebConfig:

…<system.webServer><handlers><remove name="ExtensionlessUrlHandler-Integrated-4.0" /><add name="app" path="app.ashx" verb="*" type="Sanet.Kniffel.Server.ClientRequestHandler" /><add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" resourceType="Unspecified" requireAccess="Script" preCondition="integratedMode, runtimeVersionv4.0" /></handlers></system.webServer>…

So, when you access the (virtual) "app.ashx" page, the code from the "Sanet.Kniffel.Server.ClientRequestHandler" class will be called on the server. This code is :

public class ClientRequestHandler : IHttpHandler{public void ProcessRequest(HttpContext context){if (context.IsWebSocketRequest) //request via WebSocketcontext.AcceptWebSocketRequest(new Func<AspNetWebSocketContext, Task> (MyWebSocket));else //request via Httpcontext.Response.Output.Write("There is nothing here...");}public async Task MyWebSocket(AspNetWebSocketContext context){string playerId = context.QueryString["playerId"];if (playerId == null) playerId = string.Empty;try{WebSocket socket = context.WebSocket;//new class inherited from WSCommunicator with additional functionality to connect the client to the gameServerClientLobby clientLobby = null;if (!string.IsNullOrEmpty(playerId)){//check if a client with this ID is already connectedif ( ! ServerClientLobby.playerToServerClientLobbyMapping.TryGetValue(playerId, out clientLobby)){//if there is no clientLobby - create a new oneclientLobby = new ServerClientLobby(ServerLobby, playerId);ServerClientLobby.playerToServerClientLobbyMapping.TryAdd(playerId, clientLobby);}}else{//query with an empty idreturn;}//set a new websocket and startclientLobby.WebSocket = socket;await clientLobby.Start();}catch (Exception ex){//something went wrong...}}}

I think it should be clear, given the comments. The WSCommunicator.Start() method starts the "waiting mode" of the command from the client. This is what it looks like ():

public async Task Start(){if (Interlocked.CompareExchange(ref isRunning, 1, 0) == 0){await Run();}}protected virtual async Task Run(){while (WebSocket != null WebSocket.State == WebSocketState.Open){try{string result = await Receive();if (result == null){return;}}catch (OperationCanceledException) //it's normal to cancel an operation{ }catch (Exception e){//something irreparable//close connectionCloseConnections();//advertise everyone that the client is disconnected from the gameOnReceiveCrashed(e);}}}

This is the general part, a further description of the server I omit, as it will depend more on the game you do. I will only say that commands via WebSocket are transmitted (among others) in text format. The specific implementation of these commands again depends mostly on the game. When receiving a command from the client, it will be handled by the WSCommunicator.Receive() method, to send to the client – WSCommunicator.Send(). Anything in between again depends on the logic of the game.

3. Client

3.1 WinRT.

If the client was on full .NET 4.5, you could use the same class for it WSCommunicator As for the server, we could use the same ClientWebSocket class instead of the WebSocket class and add some client-server request logic. But WinRT uses its own implementation of web sockets with StreamWebSocket and MessageWebSocket classes. The second one is used to send text messages. Here is the code to establish a connection to the server using it :

public async Task<bool> ConnectAsync(string id, bool isreconnect = false){try{//handle the local copy of the websocket to avoid closing it from another thread during the asynchronous operation//(unlikely, but possible)MessageWebSocket webSocket = ClientWebSocket;// check that we aren't connectedif (!IsConnected){//receive the server address (ws://myserver/app.ashx")var uri = ServerUri();webSocket = new MessageWebSocket();webSocket.Control.MessageType = SocketMessageType.Utf8;//set handlerswebSocket.MessageReceived += Receive;webSocket.Closed += webSocket_Closed;await webSocket.ConnectAsync(uri);ClientWebSocket = webSocket; //set in a class variable only after a successful connectionif (Connected != null)Connected(); //we tell it that we connectedreturn true;}return false;}catch (Exception e){//something is wrongreturn false;}}

Then everything is as on the server: WSCommunicator.Receive() receives messages from the server, WSCommunicator.Send() sends. GameClient works according to the data received from the server and from the user.

3.2 Windows Phone, Xamarin and Silverlight (and .NET 2.0)

All of these platforms don’t have out-of-the-box support for websockets. Fortunately there is an excellent opensource library WebSocket4Net which I mentioned in a previous article. By substituting in the WSCommunicatar e websocket class to the one implemented in this library, we will be able to connect to the server from the specified platforms. Here’s how the code to set up the connection will change :

public async Task<bool> ConnectAsync(string id, bool isreconnect = false){try{//handle the local copy of the websocket to avoid closing it from another thread during the asynchronous operation//(unlikely, but possible)WebSocket webSocket = ClientWebSocket;// check that they are not connectedif (!IsConnected){//Get the server address (ws://myserver/app.ashx")var uri = ServerUri();webSocket = new WebSocket(uri.ToString());//set handlerswebSocket.Error += webSocket_Error;webSocket.MessageReceived += Receive;webSocket.Closed += webSocket_Closed;//the connection is not asynchronous, so we force "asynchronize" itvar tcs = new TaskCompletionSource<bool> ();webSocket.Opened += (s, e) =>{//set in a class variable only after a successful connectionClientWebSocket = webSocket;if (Connected != null)Connected(); //advertise that we have connectedelse tcs.SetResult(true);};webSocket.Open();return await tcs.Task;}return false;}catch (Exception ex){//something is wrongreturn false;}}

As you can see there are some differences, but not so much, the main difference is that there is no asynchronous opening of the connection to the server, but this can be easily fixed (although for async await support in older versions of .NET you need to install Microsoft.Bcl package from nouget).

In lieu of a conclusion

I read what I wrote and realize that there are probably more questions than answers. Unfortunately, it is physically impossible to describe everything in one article, and it already turns out to be not the shortest… but I will continue to practice.

You may also like