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:
2015-07-01 06:57:30 +02:00
parent c8960df2b3
commit 2c4d95af2f
11 changed files with 248 additions and 205 deletions

View File

@ -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}

View File

@ -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);
}
}