The play "Development of a multiplayer online game." Part 2: This is the terrible word "protocol"



Part 1: Architecture
Part 3: Client-server interaction
Part 4: Moving to 3D

So, let's continue creating a multi-player game.
Today we will consider creating a data transfer protocol.
And also create the TCP server blanks and, accordingly, the client.



Part two. Step One: Data Transfer Protocol.



Just in case, I remind particularly boring readers that we are not developing quake, SC, or something like that. The main task is to show general principles and approaches using an example of a working application.

In appearance, it sounds very scary and complicated “creating a data transfer protocol”, but the devil isn’t so scary as he is painted ...

What is generally understood by a protocol in game dev? No, this is not your own bike implementation .sockets ... and not your own http protocol ... In general, this is a way of packing data for transmission between the server and the client. For example, you have a Data object that stores information about the position of your tank on the map. How to transfer this information to the server? There is a standard serialization option. Many programming languages ​​make the serialization / deserialization process very simple. Just a couple of lines of code. And it will work ... But as always there are pitfalls. For example, the speed of serialization / deserialization, the size of the received object ... and if the speed can still be neglected for the client, then the server will have to be tight, because it will have to parse messages from each client ... and nobody needs an extra load on the network. There is another point, this is cross-platform. Here in our case the server is on scala, and the client is on flash.

Therefore, there are different implementations of this process. Globally, they can be divided into two types: binary and text. Textual ones like XML and JSON. It is very easy to work with them. XML is probably the most popular option. But for real-time games, they are not suitable. The high overhead of parsing XML and the very large amount of data transferred (in xml markup takes up a lot of space) actually put an end to its use. JSON is more lightweight, markup takes up much less space than XML, but it still cannot be compared with binary protocols. Although for turn-based games, they are quite suitable.

As for binary protocols, there is no markup in them as in XML or JSON, which significantly reduces the amount of transmitted data and the time it takes to parse a message. Examples of binary protocols are AMF, protobuf. Protobuf is generally very good. It was developed and used by Google in its services. It has an implementation in many languages. And in the general case, I would advise using it or its analogues.

But we are studying here and we have a relatively simple project. Therefore, we will make our comfortable bike.

Let's imagine that we generally need to transfer between the server and the client?

Messages from the client to the server
1. Authorization (login)
2. Coordinates of the tank during movement (x, y)
3. Shot coordinates (x, y) the place where the player fired to calculate whether there was a hit on the server
4. The command to request the number of players on the server
5. The command to exit the game

Messages from the server to the client
1. Confirmation authorization
2. Tank coordinates the enemy and his condition (alive / killed), (x, y, s)
3. The team “got into us”
4. Number of players
5. Disconnecting the client by request

Summing up, we get that you can fit all the transmitted data into 7 bytes.



This is the main transmitted packet with the coordinates of the tank when moving and when firing. In the case when we need to transfer other data, we will place them in these 7 bytes. For example, we will transfer the number of active users instead of the X, Y coordinates.

As a result, we got a very compact, fast, albeit very simplified version, but for our purposes it is quite suitable. After all, we have very few transmitted data.

In the future, we will improve our protocol. But it will be in the following parts.

Part two. Action two: TCP server.



At the moment, we need to create a server stub. The tasks are as follows:
1. Connecting clients
2. Assigning an ID to the connected client

Everything, at this stage we do not need anything else, so the code is very simple.
Here we created a scala application and connection handler. The code is not the ultimate truth. I will try to write it in the most understandable way for everyone, without optimizations. Therefore, do not kick much, and if there is constructive criticism, as they say, welcome to kamenty. Hope to explain the code inappropriately? He is very simple. This is just a blank, so things like the port number for hardcodes. In the next part, we will carry out refactoring, combing our server.

 def main(args: Array[String]): Unit =
 {
  val port = 7777
  
  try
  {
   val listener = new ServerSocket(port)
   var numClients = 1
   
   println("Listening on port " + port)
   
   while (true)
   {
    new ClientHandler(listener.accept(), numClients).start()
    numClients += 1
   }
   
   listener.close()
  }
  catch
  {
   case e: IOException =>
    System.err.println( "Could not listen on port: " + port + "." )
    System.exit(-1)
  }
 }

* This source code was highlighted with Source Code Highlighter.




class ClientHandler (socket : Socket, clientId : Int) extends Actor
{
 def act
 {
  try
  {
   val out = new PrintWriter( socket.getOutputStream(), true )
   val in = new BufferedReader( new InputStreamReader(socket.getInputStream()) )

   print( "Client connected from " + socket.getInetAddress() + ":" + socket.getPort )
   println( " assigning id " + clientId)

   var inputLine = in.readLine()
   while (inputLine != null)
   { 
    println(clientId + ") " + inputLine)

    inputLine = in.readLine()
   }

   socket.close()

   println("Client " + clientId + " exit")
  }
  catch
  {
   case e: SocketException => System.err.println(e)

   case e: IOException => System.err.println(e.getMessage)

   case e => System.err.println("Unknown error " + e)
  }
 }
}

* This source code was highlighted with Source Code Highlighter.








Part two. Action Three: Client.



To implement and verify the protocol, we will also need the client’s procurement.
The tasks required at this stage are very simple:
1. Connect to the server.
2. Display your condition.
And that's it ... we don’t need anything more now.

The client code is also very simple and I will not comment on it. So everything is ready to make the first connection between the server and the client. We start the server, start the client ... and voila ... Result on the client We see that the client has successfully connected to the server. Result on the server We see that the server started on port 7777, received a connection from the client and gave it an ID. That's all for today.

  public class Main extends Sprite
  {
    public var socket:Socket = new Socket();
    
    public var host:String   = "127.0.0.1";
    public var port:int     = 7777;
    
    public var status:TextField = new TextField();
    
    public function Main():void
    {
      if (stage) init();
      else addEventListener(Event.ADDED_TO_STAGE, init);
    }

    private function init(e:Event = null):void
    {
      removeEventListener(Event.ADDED_TO_STAGE, init);
      // entry point
      
      status.text = "Player";
      addChild( status );
      
      socket.addEventListener(Event.CONNECT, socketConnectHandler);
      socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
      socket.addEventListener(DataEvent.DATA, dataHandler);  
      
      socket.connect(host, port);
    }
    
    
    //Подключение серверу
    private function socketConnectHandler(e:Event):void
    {
      status.text = "Player - connectrd";
    }
    
    //Ошибка при подключении
    private function ioErrorHandler(e:IOErrorEvent):void
    {
      status.text = "Player - error";
    }
    
    //Пришли данные от сервера
    private function dataHandler(e:DataEvent):void
    {
      switch (e.data)
      {
        // Здесь будем обрабатывать данные
      }
    }
    
    //Отправляем сообщение
    public function sendMessage(val:String):void {
      if (val != "" && socket.connected)
      {
        //socket.writeBytes( val);
      }
    }
    
    public function exitGame():void
    {
      if (socket.connected)
      {
        socket.close();
      }
    }    

  }

* This source code was highlighted with Source Code Highlighter.


















In the next part, we will implement the protocol, and actually check the resulting client server interaction.

As always, all sources can be viewed on Github

upd. When commenting, please note that this is not a tutorial “how to write your quake in 5 minutes” ... this is a sequential, from simple to complex, presentation of the development of a network game. The initial task is to make network communication between the Scala server and the Flash client. And then based on this interaction make a game.

Also popular now: