我试图为一个小服务器编写数据包监听器代码。我是Java的新手,这是我第一次搞乱网络。整个想法是它重新接收数据包,将数据包ID与它的类匹配,将输入流传递给数据包的构造函数,以便可以构造它然后将它提供给packetHander,它将具有每个数据包的重叠方法。要使用将数据包ID映射到每个类的数组,并使用一个名为decode的方法来构造数据包,来实现这个im。问题是handlePacket超载它的行为不符合预期。让我们看一些代码。
我在线程中运行数据包侦听器,run方法如下所示:
public void run() {
try {
int packet_id;
while ((packet_id = istream.readInt()) != -1) {
plugin.getServer().getConsoleSender().sendMessage("[Comm] Recived packet " + packet_id);
Packet packet = decode(packet_id, istream);
plugin.getServer().getConsoleSender().sendMessage("[Comm] Packet is " + Class.forName(packet.getClass().getName()));
plugin.getServer().getConsoleSender().sendMessage("[Comm] Class is " + Packet00ReqIdentify.class.cast(packet).getClass().getName());
plugin.getServer().getConsoleSender().sendMessage("[Comm] Class is " + Class.forName(packet.getClass().getName()).getName());
handlePacket(packet);
}
} catch (IOException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassNotFoundException e) {
e.printStackTrace();
}
}
decode和handlePacket方法如下所示:
private void handlePacket(Packet00ReqIdentify packet) throws IOException {
plugin.getServer().getConsoleSender().sendMessage("[Comm] Got it!");
}
private void handlePacket(Packet packet) {
plugin.getServer().getConsoleSender().sendMessage("[Comm] Woops!");
}
private Packet decode(int packet_id, PacketInputStream istream) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException, IOException {
Class<? extends Packet> packet_class = packets_ids.get(packet_id);
try {
Constructor<?> packet_constructor = packet_class.getConstructor(PacketInputStream.class);
return Class.forName(packet_class.getName()).asSubclass(Packet.class).cast(packet_constructor.newInstance(istream));
} catch (NoSuchMethodException e) {
return Class.forName(packet_class.getName()).asSubclass(Packet.class).cast(packet_class.newInstance());
}
}
packets_ids它是一个数组,包含对每个数据包类的引用,由他们的id标记:
private static ArrayList<Class<? extends Packet>> packets_ids;
以这种方式初始化:
private static void registerPacket(int id, Class<? extends Packet> oclass) {
packets_ids.add(id, oclass);
}
static {
packets_ids = new ArrayList<Class<? extends Packet>>();
registerPacket(Packet00ReqIdentify.assigned_pid, Packet00ReqIdentify.class);
registerPacket(Packet01Identify.assigned_pid, Packet01Identify.class);
registerPacket(Packet02Heartbeat.assigned_pid, Packet02Heartbeat.class);
}
如果我执行此操作并测试它发送类型为00的数据包,我得到这个:
17:37:49 [INFO] [Comm] Connection established to localhost:11000
17:37:49 [INFO] [Comm] Recived packet 0
17:37:49 [INFO] [Comm] Packet is class com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Class is com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Class is com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Woops!
因此,这意味着packet00尚未被&#34; handlePacket(Packet00ReqIdentify数据包)&#34;所覆盖。如果我对&#34;数据包&#34;进行显式转换在handlePacket中调用它有效。所以问题是:
为什么这不起作用?当我打印两个类名时,我得到相同的。
我怎样才能让它发挥作用?我现在一直在努力工作6到7个小时,阅读,谷歌搜索,尝试和查看其他人的代码。一个更简单的解决方案是使用数据包ID进行切换但我想要更优雅的东西。 Maybye我错了基本想法,这就是我发布代码的原因,我对这个主题中更有经验的人的建议和想法持开放态度,包括对主题材料的推荐。 / p>
谢谢!
答案 0 :(得分:3)
在每个Packet
子类中,实现一个方法public void handle()
,它可以完成处理数据包所需的工作。任
handle()
,或 Packet
的默认实施方式
handle()
中将Packet
声明为摘要,并将Packet
设为抽象类,或 handle()
中声明Packet
并使Packet
成为界面。然后替换
handlePacket(packet);
带
packet.handle();
这是行动中的多态性。它将在运行时工作,检查packet
引用的对象的类,并调用handle
方法的正确版本。
如果handle()
需要访问原始PacketListener
,请将其声明为public void handle(PacketListener listener)
并将其称为packet.handle(this);
答案 1 :(得分:2)
这是你的问题:
Packet packet = decode(packet_id, istream);
--snip--
handlePacket(packet);
由于packet
被定义为Packet
,它被路由到handlePacket(Packet packet)
方法,即使运行时类型是Packet的子类。
答案 2 :(得分:2)
你可以这样做(需要java8)
static Map<Class<?>, Consumer<?>> handlers = new HashMap<>();
void handlePacket(Packet packet)
{
Consumer<Packet> handler = (Consumer<Packet>)handlers.get(packet.getClass());
handler.accept(packet);
}
static
{
handlers.put(Packet00ReqIdentify.class, (Packet00ReqIdentify packet)->{
System.out.println("Packet00ReqIdentify");
});
handlers.put(Packet01Identify.class, (Packet01Identify packet)->{
System.out.println("Packet01Identify");
});
// etc.
}
这个用例“双重发送”很频繁,我们应该为它做一个通用的工具,比如
public class DoubleDispatch<T, R>
{
public R invoke(T obj){...}
public <C extends T> void register(Class<C> type, Function<C,R> func){...}
}
可以用来解决这个问题:
DoubleDispatch<Packet,Void> dd = new DoubleDispatch<>();
dd.register(Packet00ReqIdentify.class, packet->{
System.out.println("Packet00ReqIdentify");
return null;
});
// etc
...
dd.invoke(packet);
如果没有lambda表达式,我们可以使用匿名内部类来实现类似
的东西 dd.new Handler<Packet00ReqIdentify>(){
public Void handle(Packet00ReqIdentify obj) {
System.out.println("Packet00ReqIdentify");
return null;
}
};