问题
I'm trying to code a packet listener for a little server. I'm very new to Java and this is the first time i mess around with networking. The whole idea it's recive the packet, match the packet id with it's class, pass the input stream to the constructor of the packet so it can be constructed and then give it to the packetHander, wich will have an overladed method for each packet. To achive this im using an array that maps the packets ids to the classes of each one, and using a method called decode to construct the packet. The problem it's the overloading of handlePacket it's not behaving as expected. Lets see some code.
I've the packet listener running in a thread and the run method looks like this:
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();
}
}
The decode and handlePacket methods looks like this:
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 it's an array that contains the reference to the class of each packet, indexed by theirs ids:
private static ArrayList<Class<? extends Packet>> packets_ids;
It gets initialized this way:
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);
}
If i execute this and test it sending a packet of type 00, i get this:
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!
So it means the packet00 has not been cathed by "handlePacket(Packet00ReqIdentify packet)". If I make an explicit cast to "packet" in handlePacket call it works. So the questions are:
Why this it's not working? When i print the class names for both i get the same.
How can I make it work? I been struggling with this for 6 or 7 hours for now, reading, googling, trying and seeing code from others. One simpler solution it's to make an switch using the packet id but i want something more elegant. Maybye i'm wrong from the base idea so that's why i posted the code, i'm open to suggestions and ideas of more experienced people in the subject, incluiding recomendations of material in the subject.
Thanks!
回答1:
In each of your Packet
subclasses, implement a method public void handle()
which does what you need to handle the packet. Either
- Put a default implementation of
handle()
inPacket
, or - Declare
handle()
as abstract inPacket
, and makePacket
an abstract class, or - Declare
handle()
inPacket
and makePacket
an interface.
Then replace
handlePacket(packet);
with
packet.handle();
This is polymorphism in action. It will work at run time, checking the class of the object that packet
references, and calling the right version of the handle
method.
If handle()
needs access to the original PacketListener
, then declare it as public void handle(PacketListener listener)
and call it as packet.handle(this);
回答2:
Here's your problem:
Packet packet = decode(packet_id, istream);
--snip--
handlePacket(packet);
Since packet
is defined as a Packet
it gets routed to the handlePacket(Packet packet)
method even though the runtime type is a subclass of Packet.
回答3:
You may do this (requires 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.
}
This use case "double dispatch" is frequent enough, we should make a general utility for it, like
public class DoubleDispatch<T, R>
{
public R invoke(T obj){...}
public <C extends T> void register(Class<C> type, Function<C,R> func){...}
}
Which can be used to solve this problem:
DoubleDispatch<Packet,Void> dd = new DoubleDispatch<>();
dd.register(Packet00ReqIdentify.class, packet->{
System.out.println("Packet00ReqIdentify");
return null;
});
// etc
...
dd.invoke(packet);
Without lambda expression, we could use anonymous inner class, to achieve something like
dd.new Handler<Packet00ReqIdentify>(){
public Void handle(Packet00ReqIdentify obj) {
System.out.println("Packet00ReqIdentify");
return null;
}
};
来源:https://stackoverflow.com/questions/21032017/java-class-cast-and-overload