async UDP ping
This commit is contained in:
@@ -5,12 +5,10 @@ import arc.func.*;
|
|||||||
import arc.math.*;
|
import arc.math.*;
|
||||||
import arc.net.*;
|
import arc.net.*;
|
||||||
import arc.net.FrameworkMessage.*;
|
import arc.net.FrameworkMessage.*;
|
||||||
import arc.net.dns.*;
|
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import arc.util.Log.*;
|
import arc.util.Log.*;
|
||||||
import arc.util.io.*;
|
import arc.util.io.*;
|
||||||
import mindustry.*;
|
|
||||||
import mindustry.game.EventType.*;
|
import mindustry.game.EventType.*;
|
||||||
import mindustry.net.Administration.*;
|
import mindustry.net.Administration.*;
|
||||||
import mindustry.net.Net.*;
|
import mindustry.net.Net.*;
|
||||||
@@ -211,8 +209,23 @@ public class ArcNetProvider implements NetProvider{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO remove
|
||||||
|
static AsyncUdp udp = new AsyncUdp();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void pingHost(String address, int port, Cons<Host> valid, Cons<Exception> invalid){
|
public void pingHost(String address, int port, Cons<Host> valid, Cons<Exception> invalid){
|
||||||
|
long time = Time.millis();
|
||||||
|
var socket = new InetSocketAddress(address, port);
|
||||||
|
Log.info("Time to resolve @: @", address, Time.timeSinceMillis(time));
|
||||||
|
|
||||||
|
udp.send(socket, 2000, 512, ByteBuffer.wrap(new byte[]{-2, 1}), data -> {
|
||||||
|
Host host = NetworkIO.readServerData((int)Time.timeSinceMillis(time), socket.getAddress().getHostAddress(), data);
|
||||||
|
host.port = port;
|
||||||
|
Core.app.post(() -> valid.get(host));
|
||||||
|
}, e -> Core.app.post(() -> invalid.get(e)));
|
||||||
|
|
||||||
|
/*
|
||||||
try{
|
try{
|
||||||
var host = pingHostImpl(address, port);
|
var host = pingHostImpl(address, port);
|
||||||
Core.app.post(() -> valid.get(host));
|
Core.app.post(() -> valid.get(host));
|
||||||
@@ -228,7 +241,7 @@ public class ArcNetProvider implements NetProvider{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Core.app.post(() -> invalid.get(e));
|
Core.app.post(() -> invalid.get(e));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private Host pingHostImpl(String address, int port) throws IOException{
|
private Host pingHostImpl(String address, int port) throws IOException{
|
||||||
|
|||||||
161
core/src/mindustry/net/AsyncUdp.java
Normal file
161
core/src/mindustry/net/AsyncUdp.java
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package mindustry.net;
|
||||||
|
|
||||||
|
import arc.func.*;
|
||||||
|
import arc.util.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.*;
|
||||||
|
import java.nio.channels.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class AsyncUdp implements Runnable{
|
||||||
|
Selector selector;
|
||||||
|
DelayQueue<Request> removals = new DelayQueue<>();
|
||||||
|
TaskQueue tasks = new TaskQueue();
|
||||||
|
int emptySelects;
|
||||||
|
|
||||||
|
public AsyncUdp(){
|
||||||
|
try{
|
||||||
|
selector = Selector.open();
|
||||||
|
|
||||||
|
Threads.daemon("AsyncUDP", this);
|
||||||
|
Threads.daemon("AsyncUDP-Delay", () -> {
|
||||||
|
while(true){
|
||||||
|
try{
|
||||||
|
var request = removals.take();
|
||||||
|
synchronized(request){
|
||||||
|
request.cancel(new TimeoutException());
|
||||||
|
}
|
||||||
|
}catch(InterruptedException ignored){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}catch(IOException e){
|
||||||
|
throw new ArcRuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(InetSocketAddress address, int timeout, int bufferSize, ByteBuffer data, Cons<ByteBuffer> received, Cons<Exception> failed){
|
||||||
|
//TODO is it worth posting to the task queue when you can just run it here? shouldn't be very expensive
|
||||||
|
tasks.post(() -> {
|
||||||
|
try{
|
||||||
|
DatagramChannel channel = selector.provider().openDatagramChannel();
|
||||||
|
channel.configureBlocking(false);
|
||||||
|
channel.connect(address);
|
||||||
|
channel.send(data, address);
|
||||||
|
|
||||||
|
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
|
||||||
|
|
||||||
|
Request req = new Request(address, timeout, bufferSize, data, channel, key, received, failed);
|
||||||
|
key.attach(req);
|
||||||
|
|
||||||
|
removals.offer(req);
|
||||||
|
}catch(Exception e){
|
||||||
|
Log.err(e);
|
||||||
|
failed.get(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(){
|
||||||
|
while(true){
|
||||||
|
try{
|
||||||
|
long startTime = Time.millis();
|
||||||
|
int selected = selector.select(0);
|
||||||
|
|
||||||
|
tasks.run();
|
||||||
|
|
||||||
|
if(selected == 0){
|
||||||
|
//prevent hogging the CPU due to empty selects as per Kryonet implementation
|
||||||
|
if(emptySelects++ >= 100){
|
||||||
|
emptySelects = 0;
|
||||||
|
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||||
|
if(elapsedTime < 25) Threads.sleep(25 - elapsedTime);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = selector.selectedKeys();
|
||||||
|
for(Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext(); ){
|
||||||
|
var key = iter.next();
|
||||||
|
iter.remove();
|
||||||
|
|
||||||
|
if(key.isReadable()){
|
||||||
|
var request = (Request)key.attachment();
|
||||||
|
|
||||||
|
synchronized(request){
|
||||||
|
try{
|
||||||
|
var channel = (DatagramChannel)key.channel();
|
||||||
|
var buffer = ByteBuffer.allocate(request.bufferSize);
|
||||||
|
channel.receive(buffer);
|
||||||
|
buffer.flip();
|
||||||
|
|
||||||
|
request.received.get(buffer);
|
||||||
|
request.close();
|
||||||
|
}catch(IOException error){
|
||||||
|
request.cancel(error);
|
||||||
|
Log.err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}catch(IOException e){
|
||||||
|
Log.err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Request implements Delayed{
|
||||||
|
final InetSocketAddress address;
|
||||||
|
final long timeout, connectStartMs;
|
||||||
|
final int bufferSize;
|
||||||
|
final ByteBuffer data;
|
||||||
|
final Cons<ByteBuffer> received;
|
||||||
|
final Cons<Exception> failed;
|
||||||
|
final DatagramChannel channel;
|
||||||
|
final SelectionKey key;
|
||||||
|
|
||||||
|
public Request(InetSocketAddress address, long timeout, int bufferSize, ByteBuffer data, DatagramChannel channel, SelectionKey key, Cons<ByteBuffer> received, Cons<Exception> failed){
|
||||||
|
this.address = address;
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.bufferSize = bufferSize;
|
||||||
|
this.data = data;
|
||||||
|
this.received = received;
|
||||||
|
this.failed = failed;
|
||||||
|
this.channel = channel;
|
||||||
|
this.key = key;
|
||||||
|
this.connectStartMs = Time.millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close(){
|
||||||
|
if(!key.isValid()) return;
|
||||||
|
try{
|
||||||
|
key.cancel();
|
||||||
|
channel.close();
|
||||||
|
}catch(Exception close){
|
||||||
|
close.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel(Exception error){
|
||||||
|
if(!key.isValid()) return;
|
||||||
|
failed.get(error);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDelay(TimeUnit unit){
|
||||||
|
return unit.convert(timeout - Time.timeSinceMillis(connectStartMs), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Delayed o){
|
||||||
|
return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ import net.jpountz.lz4.*;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.*;
|
import java.nio.*;
|
||||||
import java.nio.channels.*;
|
import java.nio.channels.*;
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
import static arc.util.Log.*;
|
import static arc.util.Log.*;
|
||||||
import static mindustry.Vars.*;
|
import static mindustry.Vars.*;
|
||||||
@@ -34,7 +33,6 @@ public class Net{
|
|||||||
private final ObjectMap<Class<?>, Cons> clientListeners = new ObjectMap<>();
|
private final ObjectMap<Class<?>, Cons> clientListeners = new ObjectMap<>();
|
||||||
private final ObjectMap<Class<?>, Cons2<NetConnection, Object>> serverListeners = new ObjectMap<>();
|
private final ObjectMap<Class<?>, Cons2<NetConnection, Object>> serverListeners = new ObjectMap<>();
|
||||||
private final IntMap<StreamBuilder> streams = new IntMap<>();
|
private final IntMap<StreamBuilder> streams = new IntMap<>();
|
||||||
private final ExecutorService pingExecutor = OS.isWindows && !OS.is64Bit ? Threads.boundedExecutor("Ping Servers", 5) : Threads.unboundedExecutor();
|
|
||||||
|
|
||||||
private final NetProvider provider;
|
private final NetProvider provider;
|
||||||
|
|
||||||
@@ -330,7 +328,7 @@ public class Net{
|
|||||||
* If the port is the default mindustry port, SRV records are checked too.
|
* If the port is the default mindustry port, SRV records are checked too.
|
||||||
*/
|
*/
|
||||||
public void pingHost(String address, int port, Cons<Host> valid, Cons<Exception> failed){
|
public void pingHost(String address, int port, Cons<Host> valid, Cons<Exception> failed){
|
||||||
pingExecutor.submit(() -> provider.pingHost(address, port, valid, failed));
|
provider.pingHost(address, port, valid, failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user