smarter network code, fixed various issues
- deciding on the stream at creation time is just silly - it should be done based on the identities (this part is TODO) - changed NodeRegistry so it doesn't store nodes - this should help to connect faster - inventory items shouldn't be advertised to nodes that are already aware of them (issue #13) - objects shouldn't be requested more than once (issue #9)
This commit is contained in:
@ -37,14 +37,14 @@ import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
|
||||
import static ch.dissem.bitmessage.networking.Connection.State.*;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
||||
|
||||
/**
|
||||
* A connection to a specific node
|
||||
@ -53,41 +53,44 @@ public class Connection implements Runnable {
|
||||
public static final int READ_TIMEOUT = 2000;
|
||||
private final static Logger LOG = LoggerFactory.getLogger(Connection.class);
|
||||
private static final int CONNECT_TIMEOUT = 5000;
|
||||
private final ConcurrentMap<InventoryVector, Long> ivCache;
|
||||
private InternalContext ctx;
|
||||
|
||||
private Mode mode;
|
||||
private State state;
|
||||
private Socket socket;
|
||||
private InputStream in;
|
||||
private OutputStream out;
|
||||
private MessageListener listener;
|
||||
|
||||
private int version;
|
||||
private long[] streams;
|
||||
|
||||
private NetworkAddress host;
|
||||
private NetworkAddress node;
|
||||
|
||||
private Queue<MessagePayload> sendingQueue = new ConcurrentLinkedDeque<>();
|
||||
private ConcurrentMap<InventoryVector, Long> requestedObjects;
|
||||
|
||||
public Connection(InternalContext context, Mode mode, Socket socket, MessageListener listener) throws IOException {
|
||||
this.ctx = context;
|
||||
this.mode = mode;
|
||||
this.state = CONNECTING;
|
||||
public Connection(InternalContext context, Mode mode, Socket socket, MessageListener listener,
|
||||
ConcurrentMap<InventoryVector, Long> requestedObjectsMap) throws IOException {
|
||||
this(context, mode, listener, requestedObjectsMap);
|
||||
this.socket = socket;
|
||||
this.listener = listener;
|
||||
this.host = new NetworkAddress.Builder().ipv6(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).port(0).build();
|
||||
this.node = new NetworkAddress.Builder().ip(socket.getInetAddress()).port(socket.getPort()).stream(1).build();
|
||||
}
|
||||
|
||||
public Connection(InternalContext context, Mode mode, NetworkAddress node, MessageListener listener) {
|
||||
public Connection(InternalContext context, Mode mode, NetworkAddress node, MessageListener listener,
|
||||
ConcurrentMap<InventoryVector, Long> requestedObjectsMap) {
|
||||
this(context, mode, listener, requestedObjectsMap);
|
||||
this.socket = new Socket();
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
private Connection(InternalContext context, Mode mode, MessageListener listener,
|
||||
ConcurrentMap<InventoryVector, Long> requestedObjectsMap) {
|
||||
this.ctx = context;
|
||||
this.mode = mode;
|
||||
this.state = CONNECTING;
|
||||
this.socket = new Socket();
|
||||
this.node = node;
|
||||
this.listener = listener;
|
||||
this.requestedObjects = requestedObjectsMap;
|
||||
this.host = new NetworkAddress.Builder().ipv6(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).port(0).build();
|
||||
ivCache = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
@ -199,13 +202,66 @@ public class Connection implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupIvCache() {
|
||||
Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE);
|
||||
for (Map.Entry<InventoryVector, Long> entry : ivCache.entrySet()) {
|
||||
if (entry.getValue() < fiveMinutesAgo) {
|
||||
ivCache.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIvCache(InventoryVector... inventory) {
|
||||
cleanupIvCache();
|
||||
Long now = UnixTime.now();
|
||||
for (InventoryVector iv : inventory) {
|
||||
ivCache.put(iv, now);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIvCache(List<InventoryVector> inventory) {
|
||||
cleanupIvCache();
|
||||
Long now = UnixTime.now();
|
||||
for (InventoryVector iv : inventory) {
|
||||
ivCache.put(iv, now);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRequestedObjects(List<InventoryVector> missing) {
|
||||
Long now = UnixTime.now();
|
||||
Long fiveMinutesAgo = now - 5 * MINUTE;
|
||||
Long tenMinutesAgo = now - 10 * MINUTE;
|
||||
List<InventoryVector> stillMissing = new LinkedList<>();
|
||||
for (Map.Entry<InventoryVector, Long> entry : requestedObjects.entrySet()) {
|
||||
if (entry.getValue() < fiveMinutesAgo) {
|
||||
stillMissing.add(entry.getKey());
|
||||
// If it's still not available after 10 minutes, we won't look for it
|
||||
// any longer (except it's announced again)
|
||||
if (entry.getValue() < tenMinutesAgo) {
|
||||
requestedObjects.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (InventoryVector iv : missing) {
|
||||
requestedObjects.put(iv, now);
|
||||
}
|
||||
if (!stillMissing.isEmpty()) {
|
||||
LOG.debug(stillMissing.size() + " items are still missing.");
|
||||
missing.addAll(stillMissing);
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveMessage(MessagePayload messagePayload) {
|
||||
switch (messagePayload.getCommand()) {
|
||||
case INV:
|
||||
Inv inv = (Inv) messagePayload;
|
||||
updateIvCache(inv.getInventory());
|
||||
List<InventoryVector> missing = ctx.getInventory().getMissing(inv.getInventory(), streams);
|
||||
missing.removeAll(requestedObjects.keySet());
|
||||
LOG.debug("Received inventory with " + inv.getInventory().size() + " elements, of which are "
|
||||
+ missing.size() + " missing.");
|
||||
updateRequestedObjects(missing);
|
||||
send(new GetData.Builder().inventory(missing).build());
|
||||
break;
|
||||
case GETDATA:
|
||||
@ -276,6 +332,11 @@ public class Connection implements Runnable {
|
||||
sendingQueue.offer(new Inv.Builder()
|
||||
.addInventoryVector(iv)
|
||||
.build());
|
||||
updateIvCache(iv);
|
||||
}
|
||||
|
||||
public boolean knowsOf(InventoryVector iv) {
|
||||
return ivCache.containsKey(iv);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -291,6 +352,13 @@ public class Connection implements Runnable {
|
||||
return Objects.hash(node);
|
||||
}
|
||||
|
||||
public void request(InventoryVector key) {
|
||||
sendingQueue.offer(new GetData.Builder()
|
||||
.addInventoryVector(key)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
public enum Mode {SERVER, CLIENT}
|
||||
|
||||
public enum State {CONNECTING, ACTIVE, DISCONNECTED}
|
||||
|
@ -30,6 +30,8 @@ import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@ -43,13 +45,17 @@ import static ch.dissem.bitmessage.utils.DebugUtils.inc;
|
||||
* Handles all the networky stuff.
|
||||
*/
|
||||
public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
public final static int NETWORK_MAGIC_NUMBER = 8;
|
||||
private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class);
|
||||
private final ExecutorService pool;
|
||||
private final List<Connection> connections = new LinkedList<>();
|
||||
private InternalContext ctx;
|
||||
private ServerSocket serverSocket;
|
||||
private Thread serverThread;
|
||||
private Thread connectionManager;
|
||||
|
||||
private ConcurrentMap<InventoryVector, Long> requestedObjects = new ConcurrentHashMap<>();
|
||||
|
||||
public NetworkNode() {
|
||||
pool = Executors.newCachedThreadPool();
|
||||
}
|
||||
@ -66,18 +72,21 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
}
|
||||
try {
|
||||
serverSocket = new ServerSocket(ctx.getPort());
|
||||
pool.execute(new Runnable() {
|
||||
serverThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
socket.setSoTimeout(10000);
|
||||
startConnection(new Connection(ctx, SERVER, socket, listener));
|
||||
} catch (IOException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
while (!serverSocket.isClosed()) {
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
socket.setSoTimeout(Connection.READ_TIMEOUT);
|
||||
startConnection(new Connection(ctx, SERVER, socket, listener, requestedObjects));
|
||||
} catch (IOException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, "server");
|
||||
serverThread.start();
|
||||
connectionManager = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -96,10 +105,11 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (active < 8) {
|
||||
List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(8 - active, ctx.getStreams());
|
||||
if (active < NETWORK_MAGIC_NUMBER) {
|
||||
List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(
|
||||
NETWORK_MAGIC_NUMBER - active, ctx.getStreams());
|
||||
for (NetworkAddress address : addresses) {
|
||||
startConnection(new Connection(ctx, CLIENT, address, listener));
|
||||
startConnection(new Connection(ctx, CLIENT, address, listener, requestedObjects));
|
||||
}
|
||||
}
|
||||
Thread.sleep(30000);
|
||||
@ -126,12 +136,12 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
} catch (IOException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
pool.shutdown();
|
||||
synchronized (connections) {
|
||||
for (Connection c : connections) {
|
||||
c.disconnect();
|
||||
}
|
||||
}
|
||||
pool.shutdown();
|
||||
}
|
||||
|
||||
private void startConnection(Connection c) {
|
||||
@ -147,17 +157,17 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
|
||||
@Override
|
||||
public void offer(final InventoryVector iv) {
|
||||
List<Connection> active = new LinkedList<>();
|
||||
List<Connection> target = new LinkedList<>();
|
||||
synchronized (connections) {
|
||||
for (Connection connection : connections) {
|
||||
if (connection.getState() == ACTIVE) {
|
||||
active.add(connection);
|
||||
if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) {
|
||||
target.add(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.debug(active.size() + " connections available to offer " + iv);
|
||||
List<Connection> random8 = Collections.selectRandom(8, active);
|
||||
for (Connection connection : random8) {
|
||||
LOG.debug(target.size() + " connections available to offer " + iv);
|
||||
List<Connection> randomSubset = Collections.selectRandom(NETWORK_MAGIC_NUMBER, target);
|
||||
for (Connection connection : randomSubset) {
|
||||
connection.offer(iv);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user