Search code examples
javanetwork-programmingmultiplayerpacket

How to handle the different packets in both the server and client?


In the gameserver & client that I'm planning to make, I'm gonna have a lot of packets in the long run that I have to handle. I'm wondering what are the best-practices on how to handle my different packets.

My packets payload will start with a PacketID.

Would it be a good idea to create a seperate class extending Packet and handle the logics there? For example Packet001Login? This will give me a lot of classes in the long run.

Is it better to make a gigantic switch statement? I doubt it.

Is the best way something that I didn't think of?

Any advice is greatly appreciated.


Solution

  • If you do have enougth calculation time on the server side you should work on a technique to create prototypes for different package types.

    To illustrate the point and the idea of this i give you some UML like class descriptions

    UML:

    class PacketPrototype
    + PacketPrototype(PacketType)
    + addData(DataType, Bitlength, ...)
    + deserilize(Bitstream stream) : ReceivedPacket
    + serilizeThis() : ToSendPacket
    + getPacketType() : int
    

    you need also a class which have all PacketPrototypes and decides on the Type of each PacketPrototype object which Prototype should be used to deserilize the data.

    you need one class which knows every PacketPrototype, i call it PacketPrototypeHolder

    class PacketPrototypeHolder
    + register(PacketPrototype)
    + getPrototypeForPacketType(int Type) : PacketPrototype
    

    The Protocol on setup time is as follows

    PacketEnemy00 = new PacketPrototype(0)
    PacketEnemy00.addData(Types.Int, 5) // health
    PacketEnemy00.addData(Types.String, 16) // name
    ...
    

    this means that a packet of type 0 consists out of a int which is 5 bits long and a string which does have a maximal length of 16 characters.

    We have to add the PacketPrototype after setup to our PacketPrototypeHolder

    PacketHolder.register(PacketEnemy00)

    If the server or client receives something we read the type of the packet, then we do (you can read the data from Bitstream)

    Type = Bitstream.readInt(5)
    Prototype = PacketHolder.getPrototypeForPacketType(Type)
    
    ReceivedPacket OfReceivedPacket = Prototype.deserilize(Bitstream)
    
    // we need here a switch/if statement to determine how to handle the reading of the data
    switch(Type)
    {
      case 0: // PacketEnemy00
    

    the Prototype.deserilize call reads the data from the datastream and puts it into a ReceivedPacket Object, from there you can access your data either with index operations or with named access.

    As an example i do it with indices

       int UnitHealth = OfReceivedPacket.getInt(/* index, we want the 1st int */0);
       string UnitName = OfReceivedPacket.getString(/* index, we want the 1st string */0);
    
       and so on...
       break;
       ...
    }
    

    So effectivly i moved the switch statement from inside the network layer to the application/usage layer.

    To remove the switch you need a data driven approach. But its in the engine more complicated to realize than the hardcoded approach.