Skip to content

Instantly share code, notes, and snippets.

@Kazzuk
Last active July 15, 2023 08:13
Show Gist options
  • Save Kazzuk/d0ffb6fad7c998b4dad1e14fd60b34c7 to your computer and use it in GitHub Desktop.
Save Kazzuk/d0ffb6fad7c998b4dad1e14fd60b34c7 to your computer and use it in GitHub Desktop.
Example implementation of MCBE server pinging
package org.example;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.Promise;
import org.cloudburstmc.netty.channel.raknet.RakPong;
import org.cloudburstmc.protocol.bedrock.BedrockPong;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class PingHandler extends ChannelDuplexHandler {
private final Promise<BedrockPong> future;
private final long timeout;
private final TimeUnit timeUnit;
private ScheduledFuture<?> timeoutFuture;
public PingHandler(Promise<BedrockPong> future, long timeout, TimeUnit timeUnit) {
this.future = future;
this.timeout = timeout;
this.timeUnit = timeUnit;
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
this.timeoutFuture = ctx.channel().eventLoop().schedule(() -> {
ctx.channel().close();
this.future.tryFailure(new TimeoutException());
}, this.timeout, this.timeUnit);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!(msg instanceof RakPong pong)) {
super.channelRead(ctx, msg);
return;
}
if (this.timeoutFuture != null) {
this.timeoutFuture.cancel(false);
this.timeoutFuture = null;
}
ctx.channel().close();
this.future.trySuccess(BedrockPong.fromRakNet(pong.getPongData()));
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
super.close(ctx, promise);
if (this.timeoutFuture != null) {
this.timeoutFuture.cancel(false);
this.timeoutFuture = null;
}
}
}
public Future<BedrockPong> ping(InetSocketAddress address, long timeout, TimeUnit timeUnit) {
// Don't create a new instance of NioEventLoopGroup every time you ping,
// create it once somewhere and re-use it.
EventLoop eventLoop = new NioEventLoopGroup().next();
Promise<BedrockPong> promise = eventLoop.newPromise();
new Bootstrap()
.channelFactory(RakChannelFactory.client(NioDatagramChannel.class))
.group(eventLoop)
.option(RakChannelOption.RAK_GUID, ThreadLocalRandom.current().nextLong())
.handler(new PingHandler(promise, timeout, timeUnit))
.bind(0) // lets the system pick a port
.addListener((ChannelFuture future) -> {
if (future.isSuccess()) {
RakPing ping = new RakPing(System.currentTimeMillis(), address);
future.channel().writeAndFlush(ping).addListener(future1 -> {
if (!future1.isSuccess()) {
promise.tryFailure(future1.cause());
future.channel().close();
}
});
} else {
promise.tryFailure(future.cause());
future.channel().close();
}
});
return promise;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment