2015-03-31 21:06:42 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2015 Christian Basler
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package ch.dissem.bitmessage.networking;
|
|
|
|
|
2015-05-19 19:16:20 +02:00
|
|
|
import ch.dissem.bitmessage.InternalContext;
|
|
|
|
import ch.dissem.bitmessage.InternalContext.ContextHolder;
|
2015-12-02 17:45:50 +01:00
|
|
|
import ch.dissem.bitmessage.entity.CustomMessage;
|
2016-01-16 08:47:50 +01:00
|
|
|
import ch.dissem.bitmessage.entity.GetData;
|
2015-12-02 17:45:50 +01:00
|
|
|
import ch.dissem.bitmessage.entity.NetworkMessage;
|
2015-05-01 17:14:43 +02:00
|
|
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
2016-02-25 16:36:43 +01:00
|
|
|
import ch.dissem.bitmessage.exception.ApplicationException;
|
2015-12-02 17:45:50 +01:00
|
|
|
import ch.dissem.bitmessage.exception.NodeException;
|
|
|
|
import ch.dissem.bitmessage.factory.Factory;
|
2015-04-07 18:48:58 +02:00
|
|
|
import ch.dissem.bitmessage.ports.NetworkHandler;
|
2015-06-12 06:57:20 +02:00
|
|
|
import ch.dissem.bitmessage.utils.Collections;
|
2015-06-16 06:41:59 +02:00
|
|
|
import ch.dissem.bitmessage.utils.Property;
|
2015-03-31 21:06:42 +02:00
|
|
|
|
|
|
|
import java.io.IOException;
|
2015-08-28 13:48:01 +02:00
|
|
|
import java.net.InetAddress;
|
2015-03-31 21:06:42 +02:00
|
|
|
import java.net.Socket;
|
2015-06-16 06:41:59 +02:00
|
|
|
import java.util.*;
|
2015-11-08 10:14:37 +01:00
|
|
|
import java.util.concurrent.*;
|
2015-04-06 10:01:03 +02:00
|
|
|
|
2016-06-01 17:38:49 +02:00
|
|
|
import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER;
|
|
|
|
import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE;
|
2015-06-16 06:41:59 +02:00
|
|
|
import static ch.dissem.bitmessage.utils.DebugUtils.inc;
|
2016-04-28 07:15:48 +02:00
|
|
|
import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool;
|
2015-03-31 21:06:42 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles all the networky stuff.
|
2016-09-13 07:25:23 +02:00
|
|
|
*
|
|
|
|
* @deprecated use {@link ch.dissem.bitmessage.networking.nio.NioNetworkHandler NioNetworkHandler} instead.
|
2015-03-31 21:06:42 +02:00
|
|
|
*/
|
2016-09-13 07:25:23 +02:00
|
|
|
@Deprecated
|
2015-08-05 19:52:18 +02:00
|
|
|
public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
|
2016-01-19 21:09:46 +01:00
|
|
|
|
2016-02-26 14:34:08 +01:00
|
|
|
final Collection<Connection> connections = new ConcurrentLinkedQueue<>();
|
2016-04-28 07:15:48 +02:00
|
|
|
private final ExecutorService pool = Executors.newCachedThreadPool(
|
2016-07-25 07:52:27 +02:00
|
|
|
pool("network")
|
|
|
|
.lowPrio()
|
|
|
|
.daemon()
|
|
|
|
.build());
|
2015-05-19 19:16:20 +02:00
|
|
|
private InternalContext ctx;
|
2016-02-26 14:34:08 +01:00
|
|
|
private ServerRunnable server;
|
2015-10-14 18:37:43 +02:00
|
|
|
private volatile boolean running;
|
2015-03-31 21:06:42 +02:00
|
|
|
|
2016-10-31 06:21:36 +01:00
|
|
|
final Map<InventoryVector, Long> requestedObjects = new ConcurrentHashMap<>(50_000);
|
2015-07-01 06:57:30 +02:00
|
|
|
|
2015-04-24 11:11:08 +02:00
|
|
|
@Override
|
2015-05-19 19:16:20 +02:00
|
|
|
public void setContext(InternalContext context) {
|
2015-04-24 11:11:08 +02:00
|
|
|
this.ctx = context;
|
|
|
|
}
|
|
|
|
|
2015-09-24 08:09:20 +02:00
|
|
|
@Override
|
2016-09-12 08:18:30 +02:00
|
|
|
public Future<?> synchronize(InetAddress server, int port, long timeoutInSeconds) {
|
2015-09-24 08:09:20 +02:00
|
|
|
try {
|
2016-09-12 08:18:30 +02:00
|
|
|
Connection connection = Connection.sync(ctx, server, port, ctx.getNetworkListener(), timeoutInSeconds);
|
2015-11-08 10:14:37 +01:00
|
|
|
Future<?> reader = pool.submit(connection.getReader());
|
|
|
|
pool.execute(connection.getWriter());
|
|
|
|
return reader;
|
2015-09-24 08:09:20 +02:00
|
|
|
} catch (IOException e) {
|
2016-02-25 16:36:43 +01:00
|
|
|
throw new ApplicationException(e);
|
2015-12-02 17:45:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public CustomMessage send(InetAddress server, int port, CustomMessage request) {
|
|
|
|
try (Socket socket = new Socket(server, port)) {
|
|
|
|
socket.setSoTimeout(Connection.READ_TIMEOUT);
|
|
|
|
new NetworkMessage(request).write(socket.getOutputStream());
|
|
|
|
NetworkMessage networkMessage = Factory.getNetworkMessage(3, socket.getInputStream());
|
|
|
|
if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) {
|
|
|
|
return (CustomMessage) networkMessage.getPayload();
|
|
|
|
} else {
|
|
|
|
if (networkMessage == null) {
|
|
|
|
throw new NodeException("No response from node " + server);
|
|
|
|
} else {
|
|
|
|
throw new NodeException("Unexpected response from node " +
|
2016-07-25 07:52:27 +02:00
|
|
|
server + ": " + networkMessage.getPayload().getCommand());
|
2015-12-02 17:45:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
2016-07-25 07:52:27 +02:00
|
|
|
throw new NodeException(e.getMessage(), e);
|
2015-09-24 08:09:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-07 18:48:58 +02:00
|
|
|
@Override
|
2016-09-12 08:18:30 +02:00
|
|
|
public void start() {
|
2015-10-14 18:37:43 +02:00
|
|
|
if (running) {
|
2015-10-12 12:44:13 +02:00
|
|
|
throw new IllegalStateException("Network already running - you need to stop first.");
|
|
|
|
}
|
2015-04-07 18:48:58 +02:00
|
|
|
try {
|
2015-10-14 18:37:43 +02:00
|
|
|
running = true;
|
2015-10-12 12:44:13 +02:00
|
|
|
connections.clear();
|
2016-09-12 08:18:30 +02:00
|
|
|
server = new ServerRunnable(ctx, this);
|
2016-02-26 14:34:08 +01:00
|
|
|
pool.execute(server);
|
2016-09-12 08:18:30 +02:00
|
|
|
pool.execute(new ConnectionOrganizer(ctx, this));
|
2015-04-07 18:48:58 +02:00
|
|
|
} catch (IOException e) {
|
2016-02-25 16:36:43 +01:00
|
|
|
throw new ApplicationException(e);
|
2015-04-07 18:48:58 +02:00
|
|
|
}
|
2015-03-31 21:06:42 +02:00
|
|
|
}
|
|
|
|
|
2015-08-05 19:52:18 +02:00
|
|
|
@Override
|
|
|
|
public boolean isRunning() {
|
2015-10-14 18:37:43 +02:00
|
|
|
return running;
|
2015-08-05 19:52:18 +02:00
|
|
|
}
|
|
|
|
|
2015-04-17 13:01:46 +02:00
|
|
|
@Override
|
|
|
|
public void stop() {
|
2016-02-26 14:34:08 +01:00
|
|
|
server.close();
|
2015-04-17 13:01:46 +02:00
|
|
|
synchronized (connections) {
|
2016-02-26 14:34:08 +01:00
|
|
|
running = false;
|
2015-04-17 13:01:46 +02:00
|
|
|
for (Connection c : connections) {
|
|
|
|
c.disconnect();
|
|
|
|
}
|
|
|
|
}
|
2016-01-16 08:47:50 +01:00
|
|
|
requestedObjects.clear();
|
2015-04-17 13:01:46 +02:00
|
|
|
}
|
|
|
|
|
2016-02-26 14:34:08 +01:00
|
|
|
void startConnection(Connection c) {
|
|
|
|
if (!running) return;
|
|
|
|
|
2015-04-07 18:48:58 +02:00
|
|
|
synchronized (connections) {
|
2016-02-26 14:34:08 +01:00
|
|
|
if (!running) return;
|
|
|
|
|
2015-06-12 06:57:20 +02:00
|
|
|
// prevent connecting twice to the same node
|
|
|
|
if (connections.contains(c)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-07 18:48:58 +02:00
|
|
|
connections.add(c);
|
|
|
|
}
|
2015-10-14 18:37:43 +02:00
|
|
|
pool.execute(c.getReader());
|
|
|
|
pool.execute(c.getWriter());
|
2015-03-31 21:06:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-05-01 17:14:43 +02:00
|
|
|
public void offer(final InventoryVector iv) {
|
2015-07-01 06:57:30 +02:00
|
|
|
List<Connection> target = new LinkedList<>();
|
2016-02-26 14:34:08 +01:00
|
|
|
for (Connection connection : connections) {
|
|
|
|
if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) {
|
|
|
|
target.add(connection);
|
2015-05-01 17:14:43 +02:00
|
|
|
}
|
|
|
|
}
|
2015-07-01 06:57:30 +02:00
|
|
|
List<Connection> randomSubset = Collections.selectRandom(NETWORK_MAGIC_NUMBER, target);
|
|
|
|
for (Connection connection : randomSubset) {
|
2015-06-16 06:41:59 +02:00
|
|
|
connection.offer(iv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Property getNetworkStatus() {
|
|
|
|
TreeSet<Long> streams = new TreeSet<>();
|
|
|
|
TreeMap<Long, Integer> incomingConnections = new TreeMap<>();
|
|
|
|
TreeMap<Long, Integer> outgoingConnections = new TreeMap<>();
|
|
|
|
|
2016-02-26 14:34:08 +01:00
|
|
|
for (Connection connection : connections) {
|
|
|
|
if (connection.getState() == ACTIVE) {
|
2016-07-29 07:49:53 +02:00
|
|
|
for (long stream : connection.getStreams()) {
|
|
|
|
streams.add(stream);
|
|
|
|
if (connection.getMode() == SERVER) {
|
|
|
|
inc(incomingConnections, stream);
|
|
|
|
} else {
|
|
|
|
inc(outgoingConnections, stream);
|
|
|
|
}
|
2015-06-16 06:41:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Property[] streamProperties = new Property[streams.size()];
|
|
|
|
int i = 0;
|
|
|
|
for (Long stream : streams) {
|
|
|
|
int incoming = incomingConnections.containsKey(stream) ? incomingConnections.get(stream) : 0;
|
|
|
|
int outgoing = outgoingConnections.containsKey(stream) ? outgoingConnections.get(stream) : 0;
|
|
|
|
streamProperties[i] = new Property("stream " + stream,
|
2016-07-25 07:52:27 +02:00
|
|
|
null, new Property("nodes", incoming + outgoing),
|
|
|
|
new Property("incoming", incoming),
|
|
|
|
new Property("outgoing", outgoing)
|
2015-06-16 06:41:59 +02:00
|
|
|
);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return new Property("network", null,
|
2016-07-25 07:52:27 +02:00
|
|
|
new Property("connectionManager", running ? "running" : "stopped"),
|
|
|
|
new Property("connections", null, streamProperties),
|
|
|
|
new Property("requestedObjects", requestedObjects.size())
|
2015-06-16 06:41:59 +02:00
|
|
|
);
|
2015-03-31 21:06:42 +02:00
|
|
|
}
|
2016-01-16 08:47:50 +01:00
|
|
|
|
2016-06-01 17:38:49 +02:00
|
|
|
@Override
|
|
|
|
public void request(Collection<InventoryVector> inventoryVectors) {
|
2016-01-16 08:47:50 +01:00
|
|
|
if (!running || inventoryVectors.isEmpty()) return;
|
2016-02-26 14:34:08 +01:00
|
|
|
|
|
|
|
Map<Connection, List<InventoryVector>> distribution = new HashMap<>();
|
|
|
|
for (Connection connection : connections) {
|
|
|
|
if (connection.getState() == ACTIVE) {
|
|
|
|
distribution.put(connection, new LinkedList<InventoryVector>());
|
2016-01-19 21:09:46 +01:00
|
|
|
}
|
2016-02-26 14:34:08 +01:00
|
|
|
}
|
|
|
|
Iterator<InventoryVector> iterator = inventoryVectors.iterator();
|
|
|
|
if (!iterator.hasNext()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
InventoryVector next = iterator.next();
|
|
|
|
Connection previous = null;
|
|
|
|
do {
|
|
|
|
for (Connection connection : distribution.keySet()) {
|
|
|
|
if (connection == previous) {
|
2016-01-16 08:47:50 +01:00
|
|
|
next = iterator.next();
|
|
|
|
}
|
2016-02-26 14:34:08 +01:00
|
|
|
if (connection.knowsOf(next)) {
|
|
|
|
List<InventoryVector> ivs = distribution.get(connection);
|
|
|
|
if (ivs.size() == GetData.MAX_INVENTORY_SIZE) {
|
|
|
|
connection.send(new GetData.Builder().inventory(ivs).build());
|
|
|
|
ivs.clear();
|
|
|
|
}
|
|
|
|
ivs.add(next);
|
|
|
|
iterator.remove();
|
2016-01-16 08:47:50 +01:00
|
|
|
|
2016-02-26 14:34:08 +01:00
|
|
|
if (iterator.hasNext()) {
|
|
|
|
next = iterator.next();
|
|
|
|
previous = connection;
|
|
|
|
} else {
|
|
|
|
break;
|
2016-01-16 08:47:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-26 14:34:08 +01:00
|
|
|
} while (iterator.hasNext());
|
|
|
|
|
|
|
|
for (Connection connection : distribution.keySet()) {
|
|
|
|
List<InventoryVector> ivs = distribution.get(connection);
|
|
|
|
if (!ivs.isEmpty()) {
|
|
|
|
connection.send(new GetData.Builder().inventory(ivs).build());
|
2016-01-16 08:47:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-03-31 21:06:42 +02:00
|
|
|
}
|