Networking code, untested

This commit is contained in:
2015-04-06 10:01:03 +02:00
parent 0c4b39bdee
commit 3299d8ca4a
21 changed files with 573 additions and 151 deletions

View File

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

View File

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

View File

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

View File

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