Networking code, untested
This commit is contained in:
@ -9,5 +9,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
compile ':domain'
|
||||
compile 'com.google.guava:guava-concurrent:r03'
|
||||
testCompile 'org.slf4j:slf4j-simple:1.7.12'
|
||||
testCompile 'junit:junit:4.11'
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import ch.dissem.bitmessage.Context;
|
||||
import ch.dissem.bitmessage.entity.*;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.NetworkMessageReceiver.MessageListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
import static ch.dissem.bitmessage.networking.Connection.State.*;
|
||||
|
||||
/**
|
||||
* A connection to a specific node
|
||||
*/
|
||||
public class Connection implements Runnable {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(Connection.class);
|
||||
|
||||
private Context ctx;
|
||||
|
||||
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<>();
|
||||
|
||||
public Connection(State state, Socket socket, MessageListener listener) throws IOException {
|
||||
this.ctx = Context.getInstance();
|
||||
this.state = state;
|
||||
this.socket = socket;
|
||||
this.in = socket.getInputStream();
|
||||
this.out = socket.getOutputStream();
|
||||
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()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (state == CLIENT) {
|
||||
send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
|
||||
}
|
||||
while (state != DISCONNECTED) {
|
||||
try {
|
||||
NetworkMessage msg = Factory.getNetworkMessage(version, in);
|
||||
switch (state) {
|
||||
case ACTIVE:
|
||||
receiveMessage(msg.getPayload());
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (msg.getPayload().getCommand()) {
|
||||
case VERSION:
|
||||
Version payload = (Version) msg.getPayload();
|
||||
if (payload.getVersion() >= Context.CURRENT_VERSION) {
|
||||
this.version = payload.getVersion();
|
||||
this.streams = payload.getStreams();
|
||||
send(new VerAck());
|
||||
if (state == SERVER) {
|
||||
state = ACTIVE;
|
||||
}
|
||||
} else {
|
||||
disconnect();
|
||||
}
|
||||
break;
|
||||
case VERACK:
|
||||
if (state == CLIENT) {
|
||||
sendAddresses();
|
||||
sendInventory();
|
||||
|
||||
state = ACTIVE;
|
||||
} else {
|
||||
send(new Version.Builder().defaults().addrFrom(host).addrRecv(node).build());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Command 'version' or 'verack' expected, but was "
|
||||
+ msg.getPayload().getCommand());
|
||||
}
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
if (state == ACTIVE) {
|
||||
for (MessagePayload msg = sendingQueue.poll(); msg != null; msg = sendingQueue.poll()) {
|
||||
send(msg);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveMessage(MessagePayload messagePayload) {
|
||||
switch (messagePayload.getCommand()) {
|
||||
case INV:
|
||||
Inv inv = (Inv) messagePayload;
|
||||
List<InventoryVector> missing = ctx.getInventory().getMissing(inv.getInventory());
|
||||
send(new GetData.Builder().inventory(missing).build());
|
||||
break;
|
||||
case GETDATA:
|
||||
GetData getData = (GetData) messagePayload;
|
||||
for (InventoryVector iv : getData.getInventory()) {
|
||||
ObjectMessage om = ctx.getInventory().getObject(iv);
|
||||
sendingQueue.offer(om);
|
||||
}
|
||||
break;
|
||||
case OBJECT:
|
||||
ObjectMessage objectMessage = (ObjectMessage) messagePayload;
|
||||
ctx.getInventory().storeObject(objectMessage);
|
||||
listener.receive(objectMessage.getPayload());
|
||||
break;
|
||||
case ADDR:
|
||||
Addr addr = (Addr) messagePayload;
|
||||
ctx.getAddressRepository().offerAddresses(addr.getAddresses());
|
||||
break;
|
||||
case VERACK:
|
||||
case VERSION:
|
||||
throw new RuntimeException("Unexpectedly received '" + messagePayload.getCommand() + "' command");
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAddresses() {
|
||||
List<NetworkAddress> addresses = ctx.getAddressRepository().getKnownAddresses(1000, streams);
|
||||
send(new Addr.Builder().addresses(addresses).build());
|
||||
}
|
||||
|
||||
private void sendInventory() {
|
||||
List<InventoryVector> inventory = ctx.getInventory().getInventory(streams);
|
||||
for (int i = 0; i < inventory.size(); i += 50000) {
|
||||
sendingQueue.offer(new Inv.Builder()
|
||||
.inventory(inventory.subList(i, Math.min(inventory.size(), i + 50000)))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
try {
|
||||
state = DISCONNECTED;
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void send(MessagePayload payload) {
|
||||
try {
|
||||
new NetworkMessage(payload).write(out);
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public enum State {SERVER, CLIENT, ACTIVE, DISCONNECTED}
|
||||
}
|
@ -22,69 +22,68 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.NetworkMessageReceiver;
|
||||
import ch.dissem.bitmessage.ports.NetworkMessageSender;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import static ch.dissem.bitmessage.networking.Connection.State.CLIENT;
|
||||
import static ch.dissem.bitmessage.networking.Connection.State.SERVER;
|
||||
|
||||
/**
|
||||
* Handles all the networky stuff.
|
||||
*/
|
||||
public class NetworkNode implements NetworkMessageSender, NetworkMessageReceiver {
|
||||
private final BlockingQueue<NetworkMessage> sendingQueue = new LinkedBlockingQueue<>();
|
||||
private final ExecutorService pool;
|
||||
|
||||
private final Map<NetworkAddress, Socket> sockets = new HashMap<>();
|
||||
private final Map<NetworkAddress, Integer> versions = new HashMap<>();
|
||||
|
||||
private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class);
|
||||
/**
|
||||
* This is only to be used where it's ignored
|
||||
*/
|
||||
private final static NetworkAddress LOCALHOST = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build();
|
||||
private final ExecutorService pool;
|
||||
|
||||
public NetworkNode() {
|
||||
pool = Executors.newCachedThreadPool();
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
try {
|
||||
NetworkMessage message = sendingQueue.take();
|
||||
Socket socket = getSocket(message.getTargetNode());
|
||||
message.write(socket.getOutputStream());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "Sender");
|
||||
// TODO: sending
|
||||
// Thread sender = new Thread(new Runnable() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// while (true) {
|
||||
// try {
|
||||
// NetworkMessage message = sendingQueue.take();
|
||||
//
|
||||
// try (Socket socket = getSocket(message.getTargetNode())) {
|
||||
// message.write(socket.getOutputStream());
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// } catch (InterruptedException e) {
|
||||
// // Ignore?
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }, "Sender");
|
||||
// sender.setDaemon(true);
|
||||
// sender.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener(final int port) throws IOException {
|
||||
public void registerListener(final int port, final MessageListener listener) throws IOException {
|
||||
final ServerSocket serverSocket = new ServerSocket(port);
|
||||
pool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
NetworkAddress address = null;
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
socket.setSoTimeout(20000);
|
||||
// FIXME: addd to sockets
|
||||
registerListener(getVersion(null), socket, new MessageListener() {
|
||||
@Override
|
||||
public void receive(NetworkMessage message) {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
pool.execute(new Connection(SERVER, socket, listener));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -92,60 +91,11 @@ public class NetworkNode implements NetworkMessageSender, NetworkMessageReceiver
|
||||
|
||||
@Override
|
||||
public void registerListener(final NetworkAddress node, final MessageListener listener) throws IOException {
|
||||
final Socket socket = getSocket(node);
|
||||
final int version = getVersion(node);
|
||||
sendVersion(node);
|
||||
pool.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
registerListener(version, socket, listener);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendVersion(NetworkAddress node) {
|
||||
send(node, new NetworkMessage(node, new Version.Builder().defaults().addrFrom(LOCALHOST).addrRecv(node).build()));
|
||||
}
|
||||
|
||||
private void registerListener(int version, Socket socket, MessageListener listener) throws IOException {
|
||||
NetworkMessage message = Factory.getNetworkMessage(version, socket.getInputStream());
|
||||
if (message.getPayload() instanceof Version) {
|
||||
version = ((Version) message.getPayload()).getVersion();
|
||||
synchronized (versions) {
|
||||
|
||||
versions.put(new NetworkAddress.Builder()
|
||||
.ip(socket.getInetAddress())
|
||||
.port(socket.getPort())
|
||||
.build(), version);
|
||||
}
|
||||
}
|
||||
listener.receive(message);
|
||||
pool.execute(new Connection(CLIENT, new Socket(node.toInetAddress(), node.getPort()), listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(final NetworkAddress node, final NetworkMessage message) {
|
||||
sendingQueue.add(message);
|
||||
}
|
||||
|
||||
private Socket getSocket(NetworkAddress node) throws IOException {
|
||||
synchronized (sockets) {
|
||||
Socket socket = sockets.get(node);
|
||||
if (socket == null) {
|
||||
socket = new Socket(node.toInetAddress(), node.getPort());
|
||||
sockets.put(node, socket);
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized int getVersion(NetworkAddress node) {
|
||||
synchronized (versions) {
|
||||
Integer version = versions.get(node);
|
||||
return version == null ? 3 : version;
|
||||
}
|
||||
// TODO: sendingQueue.add(message);
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.networking;
|
||||
|
||||
import ch.dissem.bitmessage.entity.NetworkMessage;
|
||||
import ch.dissem.bitmessage.entity.Version;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.ports.NetworkMessageReceiver;
|
||||
import org.junit.Test;
|
||||
@ -34,12 +35,12 @@ public class NetworkNodeTest {
|
||||
NetworkNode net = new NetworkNode();
|
||||
net.registerListener(localhost, new NetworkMessageReceiver.MessageListener() {
|
||||
@Override
|
||||
public void receive(NetworkMessage message) {
|
||||
System.out.println(message);
|
||||
public void receive(ObjectPayload payload) {
|
||||
System.out.println(payload);
|
||||
baseThread.interrupt();
|
||||
}
|
||||
});
|
||||
NetworkMessage ver = new NetworkMessage(localhost,
|
||||
NetworkMessage ver = new NetworkMessage(
|
||||
new Version.Builder()
|
||||
.version(3)
|
||||
.services(1)
|
||||
@ -49,7 +50,8 @@ public class NetworkNodeTest {
|
||||
.nonce(-1)
|
||||
.userAgent("Test")
|
||||
.streams(1, 2)
|
||||
.build());
|
||||
.build()
|
||||
);
|
||||
net.send(localhost, ver);
|
||||
Thread.sleep(20000);
|
||||
}
|
||||
|
Reference in New Issue
Block a user