6

我在protocol buffers中指定了一个协议。传输层正在利用Netty的 Protocol Buffers 支持——意义在于 Netty 的ProtobufDecoder接受一种,并且只接受一种MessageLite类型。

现在,我想通过这个通道发送各种不同的消息类型,每个子类型都有与之关联的结构化信息。协议缓冲区没有继承机制,所以我使用了一种组合。我不确定我是否以正确的方式去做。

我的方法是用枚举对我的不同事件进行分类,并使用可选成员封装它们的差异。请参阅.proto下面的内容,为清楚起见,我对其进行了简化。

我的问题是接收代码需要在 EventType.ERROR 和 ErrorEventDetail 之间建立关联。这只是感觉有点笨拙。

简化Events.proto

package events;

option java_package = "com.example";
option java_outer_classname = "EventProtocol";

message Event {
  enum EventType {
    START = 0;
    DELEGATE = 1;
    ERROR = 2;
    STOP = 3;
  }
  required events.Event.EventType event_type = 1 [default = START];
  required int32 id = 2;
  required int64 when = 3;
  optional StartEventDetail start_event_detail = 4;
  optional DelegateEventDetail delegate_event_detail = 5;
  optional ErrorEventDetail error_event_detail = 6;
  optional StopEventDetail stop_event_detail = 7;
}

message StartEventDetail {
    required string object_name = 1;
}

message DelegateEventDetail {
    required int32 object_id = 2;
    required string task = 3;
}

message ErrorEventDetail {
  required string text = 1;
  required int32 error_code = 2;
  optional Event cause = 3;
}

message StopEventDetail {
    required int32 object_id = 2;
}

这是最优的吗?我会更好地以某种方式使用扩展,或者也许其他一些用途enum

甚至,我是否应该创建一个全新的OneToOneDecoder可以通过某种标头识别消息类型?我可以这样做,但我宁愿不...

谢谢

4

3 回答 3

7

似乎您已经非常接近/已经使用了 Google 的一种称为Union Types的 protobufs 技术

要点是您有一个专用type字段,您可以“打开”它以了解要获取的消息:

message OneMessage {
  enum Type { FOO = 1; BAR = 2; BAZ = 3; }

  // Identifies which field is filled in.
  required Type type = 1;

  // One of the following will be filled in.
  optional Foo foo = 2;
  optional Bar bar = 3;
  optional Baz baz = 4;
}

其中 Foo、Bar 和 Baz 是/可以在其他文件中定义为单独的消息。您可以打开类型以获取实际的有效负载(它是 Scala,但您可以使用 Java 做同样的事情switch):

OneMessage.getType match { 

  case OneMessage.Type.FOO => 

    val foo = OneMessage.getFoo
    // do the processing
    true

  case OneMessage.Type.BAR => 

    val bar = OneMessage.getBar
    // do the processing
    true

  case OneMessage.Type.BAZ => 

    val baz = OneMessage.getBaz
    // do the processing
    true

}
于 2011-11-09T06:02:38.993 回答
3

我最初使用扩展机制解决了同样的问题,我在此处记录

但是我发现处理扩展所需的 Java 代码非常丑陋和冗长,所以我切换到所描述的 Union 方法。代码更简洁,因为生成的 Java 代码提供了一种一次性获取和构建每条消息的方法。

我使用两种机制来决定提取哪个可选消息。当需要性能时,我使用另一个答案中也描述的 switch 方法,当性能不是问题并且我不想维护 switch 语句时,我使用反射方法,我只是为每个创建一个句柄(消息)信息。下面给出了反射方法的一个示例,在我的例子中,java 包装器是一个名为 Commands 的类,由 Netty 为我解码。它首先尝试找到一个将特定消息作为参数的处理程序,然后如果失败,它会调用一个使用骆驼案例名称的方法。为此,枚举必须是驼峰式消息的下划线名称。

// Helper that stops me having to create a switch statement for every command
// Relies on the Cmd enum naming being uppercase version of the sub message field names
// Will call the appropriate handle(Message) method by reflection
// If it is a command with no arguments, therefore no sub message it
// constructs the method name from the camelcase of the command enum
private MessageLite invokeHandler(Commands.Command cmd) throws Exception {
    Commands.Command.Cmd com= cmd.getCmd();
    //String name= CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_UNDERSCORE, com.name());
    String name= com.name().toLowerCase();
    jlog.debug("invokeHandler() - Looking up {} from {}", name, com.name());
    FieldDescriptor field= Commands.Command.getDescriptor().findFieldByName(name);
    if(field != null) {
        // if we have a matching field then extract it and call the handle method with that as a parameter
        Object c = cmd.getField(field);
        jlog.debug("invokeHandler() - {}\n{}", c.getClass().getCanonicalName(), c);
        Method m = getClass().getDeclaredMethod("handle", String.class, c.getClass());
        return (MessageLite) m.invoke(this, cmd.getUser(), c);
    }
    // else we call a method with the camelcase name of the Cmd, this is for commands that take no arguments other than the user
    String methodName= "handle"+CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, com.name());
    jlog.debug("invokeHandler() - using method: {}", methodName);
    Method m = getClass().getDeclaredMethod(methodName, String.class);
    return (MessageLite) m.invoke(this, cmd.getUser());
}
于 2011-12-19T18:15:10.130 回答
0

另一种方法是使用 protobuf 支持的扩展机制。我在联合类型太大的情况下使用这种方法。

于 2011-12-15T19:48:39.117 回答