سؤال

I'm developing an application where I would like to create a compiled and defined custom text-protocol. I.e. I want to send textstrings that represent information / actions to take in a server-client based system.

Is there any reference / best practice way to do this object oriented (in java)?

Some examples of the actual text strings that might be sent, for convenience separated with spaces (will be split by something else in the real case):

command1 <timestamp> <id> <arg1> <arg2>
command2 <timestamp> <id> <arg1> <arg2> <arg3>
command3 subcommand1 <timestamp> <id>
command3 subcommand2 <timestamp> <id>

So some things are included in all messages, but in other ways the messages could be totally different, depending on future implementations and so on.

What I would like to achieve with this is that the received textstring should be parsed with this protocol so that the parties that use it only have to pass the received text string to the protocol and then be able to switch to different functionality depending on the message type. My first thought was if it was possible to implement a base class, let's say "Message", and then subclasses for more specific message types (example Register or UpdateSomething).

Pseudo code to explain roughly what I wanted to achieve:

object msg = new Message(rawText);
switch (msg.getType()) {
    case Message.Register:
        // Register logic, data needed would be available in the msg-object.
        // Most likely a function, for instance: handleRegistration(msg.uid, msg.password)
        break;
    case Message.SomeData.Get:
        // logic/function to return SomeData, args/data needed would be available in the msg-object.
        break;
    case Message.SomeData.Update:
        // logic/function to update SomeData, args/data needed would be available in the msg-object.
        break;
    default:
        // Some default logic.
        break;

But I realized that even if I was able to parse the rawText and somehow transform it into different objects depending on its contents (How can I do this?), it wouldn't be possible to switch on object type in a nice way and I've seen that many argue that it isn't a good approach.

I can get all this to work in lots of ugly ways, that's no problem, I'm just very unsure about the correct approach to this. I'd really like to learn how to do this in a nice and usable way also considering scaling (the protocol will grow). I want the protocol to just be a package (jar) that each party (different clients and the server) can use after which no one needs to bother with what (text) is actually sent over the connection.

Any help and guidance is greatly appreciated, I'm open to take other completely different paths. Do note again however that I'm not asking for help to just "get it working". I'm asking about the best practice(s) to implement those things, or references where I and others reading this can learn how to do this.

Thanks!

EDIT:

I guess the main problem I have is the problem itself. The messages differ quite a bit from each other, meaning I will end up in quite a few classes if I want the data members to be unique. This in itself isn't really a problem, it's just the names have to be unique as well. I'd like to group them up in some hierarchy.

For example with the REGISTER type:

A REGISTER message is a type of MESSAGE. But there are also different kinds of register messages:

 REGISTER REQUEST <id> <password>
 REGISTER OK
 REGISTER ERROR <reason>

I would like to group these up in the sense that they are all register messages, but they are also different types of register messages with different payloads (ie need a different set of members if translated to objects). So if I'd like an object to be able to extract this information from variables I'd need 3 classes for this (example RegisterRequestMessage, RegisterOKMessage, RegisterErrorMessage) and it just feels as if all those classes and names could get a bit too much.

I wish to achieve:

  • Readability and usability for the developer using the protocol, when they make their switch case to see which message they received or when they make a new message they should easily (in a IDE that supports it) be able to list the message-types they can choose from.

  • Be able to extract information from a message that is unique for different message-types depending on what is being sent. I'd like the data available to such a message to be visible (again in a IDE that supports it) when the object is being used.

I guess there won't be any real smooth way here, either I'll end up with lots of classes (the problem is really lots of long names for messagtypes/classes), or I'll end up having to make it more generic, filling up a json-object or similar with the data.

Lots of classes:

handleRegisterMessage(MyLongNameSpecifiedMessageClass msg) {
    this.id = msg.id;
    this.password = msg.password;
    // etc etc
}

More generic json or such:

handleRegisterMessage(JSONObject msg) {
    this.id = msg.get("id");
    this.password = msg.get("password");
    // etc etc
}

I'm not sure if there's much more to do, but if you guys with more experience see some more elegant or easier solution here that I don't I'm happy to try it out.

EDIT 2:

Decided to use a unique class for each message type, even though there will be quite a few messages and the names might get somewhat long / ugly It felt like the most encapsulated and easy-to-use way. It will be compiled into a jar file and used that way anyways. Thanks for all suggestions though since other people might decide to take other paths.

هل كانت مفيدة؟

المحلول

If I understand what you mean, basically, a message is something that must

  • be parsed from a string,
  • be used to do something.

So, you could have something like

abstract class Message {
    public abstract void doSomething();
    private String myType; // or enum, or whatever

    public String getMyType() {
        return myType;
    }

    public static Message parse(String s) {
        // parse s
        // if s contains command 'register', return a new RegisterMessage
        // if s contains command 'someDataGet', return a new SomeDataGetMessage

        return // a newly created message;
        }
    }

    class MessageRegister extends Message {

    @Override
    public void doSomething() {
        // do what a Register message does          
    }

    public MessageRegister() {
        myType = "REGISTER";
    }

}

class MessageSomeDataGet extends Message {

    @Override
    public void doSomething() {
        // do what a SomeDataGet message does       
    }

    public MessageSomeDataGet() {
        myType = "SOMEDATAGET";
    }
}

Then, you create a new message with

Message m = Message.parse("some command");

You can switch on the message type like this:

switch (m.getMyType()) {
    case "SOMEDATAGET":
        // whatever
}

نصائح أخرى

The way I usually do this is with an interface, lets call it ConsoleProgram that takes string arguments and a HashMap that couples a string to a Program. Basically doing something similar to your switch statement and then each Program can worry about its own format when its passed the rest of the string (excluding the first program selecting part of the string)

public interface ConsoleProgram {
    public String getProgramName(); //what the command string starts with to call this program
    public void runProgram(String[] arguments);
}

Then a single class can deal with recieving strings and passing them to ConsolePrograms without ever worrying about what the ConsoleProgram actually does

public class Console{

    public static HashMap<String, ConsoleProgram> availablePrograms=new HashMap<>();


    public void registerProgram(ConsoleProgram program){

        availablePrograms.put(program.getProgramName().toLowerCase(), program);

    }


    public void recieveCommand(String command){

        String[] parts = command.split(" ");

        ConsoleProgram program=availablePrograms.get(parts[0].toLowerCase());

        if (program!=null){
            System.out.println(
                    program.runProgram(Arrays.copyOfRange(parts, 1, parts.length));

        }else{
            System.out.println("program unavailable");
            System.out.println(
                availablePrograms.get("availableprograms").runProgram(parts, this, null)
            );
        }
    }

}

This is something I myself use to support administrative functions within a game I am developing and it scales very nicely. Furthermore if a ConsoleProgram needs access to unusual things you don't need to have Console know about them, the specific ConsoleProgram can take those objects in its constructor before being registered using registerProgram

Instead of writing your own serializers/parcers I recommend using one of well-known tools that are created exactly for that.

One of my favourites is protocol buffers from google and there are many others.

With protocol buffers you would need to define your custom message formats (look at their simple java tutorial for reference), and then generate java code for it.

You would do it on both server/client sides, or, I guess you could simply include generated files along with dependency jar in your library and share it between server/clients.

This will have number of benefits :

  • You won't need to write your serializer/parser (and more importantly spend time debugging it!)
  • It is flexible, you can change/extend your messages in future
  • You are not limited to Java implementations, same message format can be understood by other servers/clients written in C++/Python.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top