* I2CP: Change from the internal pseudo-socket that was
implemented in 0.7.9 to an internal Queue that directly passes I2CPMessage objects. For in-JVM clients, this eliminates two writer threads per client and avoids the serialization/deserialization of I2CP messages.
This commit is contained in:
@ -22,6 +22,7 @@ import net.i2p.crypto.SHA256Generator;
|
||||
import net.i2p.crypto.SessionKeyManager;
|
||||
import net.i2p.crypto.TransientSessionKeyManager;
|
||||
import net.i2p.data.RoutingKeyGenerator;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.stat.StatManager;
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
@ -843,4 +844,13 @@ public class I2PAppContext {
|
||||
public boolean isRouterContext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to connect to the router in the same JVM.
|
||||
* @return always null in I2PAppContext, the client manager if in RouterContext
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public InternalClientManager internalClientManager() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageImpl;
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.internal.PoisonI2CPMessage;
|
||||
import net.i2p.util.I2PAppThread;
|
||||
|
||||
/**
|
||||
@ -50,7 +51,7 @@ class ClientWriterRunner implements Runnable {
|
||||
public void stopWriting() {
|
||||
_messagesToWrite.clear();
|
||||
try {
|
||||
_messagesToWrite.put(new PoisonMessage());
|
||||
_messagesToWrite.put(new PoisonI2CPMessage());
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
|
||||
@ -62,7 +63,7 @@ class ClientWriterRunner implements Runnable {
|
||||
} catch (InterruptedException ie) {
|
||||
continue;
|
||||
}
|
||||
if (msg.getType() == PoisonMessage.MESSAGE_TYPE)
|
||||
if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE)
|
||||
break;
|
||||
// only thread, we don't need synchronized
|
||||
try {
|
||||
@ -80,18 +81,4 @@ class ClientWriterRunner implements Runnable {
|
||||
}
|
||||
_messagesToWrite.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* End-of-stream msg used to stop the concurrent queue
|
||||
* See http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html
|
||||
*
|
||||
*/
|
||||
private static class PoisonMessage extends I2CPMessageImpl {
|
||||
public static final int MESSAGE_TYPE = 999999;
|
||||
public int getType() {
|
||||
return MESSAGE_TYPE;
|
||||
}
|
||||
public void doReadMessage(InputStream buf, int size) throws I2CPMessageException, IOException {}
|
||||
public byte[] doWriteMessage() throws I2CPMessageException, IOException { return null; }
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,10 @@ import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
import net.i2p.data.i2cp.MessagePayloadMessage;
|
||||
import net.i2p.data.i2cp.SessionId;
|
||||
import net.i2p.internal.I2CPMessageQueue;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.internal.QueuedI2CPMessageReader;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.InternalSocket;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleScheduler;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
@ -79,6 +81,13 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
/** where we pipe our messages */
|
||||
protected /* FIXME final FIXME */OutputStream _out;
|
||||
|
||||
/**
|
||||
* Used for internal connections to the router.
|
||||
* If this is set, _socket, _writer, and _out will be null.
|
||||
* @since 0.8.3
|
||||
*/
|
||||
protected I2CPMessageQueue _queue;
|
||||
|
||||
/** who we send events to */
|
||||
protected I2PSessionListener _sessionListener;
|
||||
|
||||
@ -285,17 +294,27 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
|
||||
long startConnect = _context.clock().now();
|
||||
try {
|
||||
// If we are in the router JVM, connect using the interal pseudo-socket
|
||||
_socket = InternalSocket.getSocket(_hostname, _portNum);
|
||||
// _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it.
|
||||
_out = _socket.getOutputStream();
|
||||
synchronized (_out) {
|
||||
_out.write(I2PClient.PROTOCOL_BYTE);
|
||||
_out.flush();
|
||||
// If we are in the router JVM, connect using the interal queue
|
||||
if (_context.isRouterContext()) {
|
||||
// _socket, _out, and _writer remain null
|
||||
InternalClientManager mgr = _context.internalClientManager();
|
||||
if (mgr == null)
|
||||
throw new I2PSessionException("Router is not ready for connections");
|
||||
// the following may throw an I2PSessionException
|
||||
_queue = mgr.connect();
|
||||
_reader = new QueuedI2CPMessageReader(_queue, this);
|
||||
} else {
|
||||
_socket = new Socket(_hostname, _portNum);
|
||||
// _socket.setSoTimeout(1000000); // Uhmmm we could really-really use a real timeout, and handle it.
|
||||
_out = _socket.getOutputStream();
|
||||
synchronized (_out) {
|
||||
_out.write(I2PClient.PROTOCOL_BYTE);
|
||||
_out.flush();
|
||||
}
|
||||
_writer = new ClientWriterRunner(_out, this);
|
||||
InputStream in = _socket.getInputStream();
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
}
|
||||
_writer = new ClientWriterRunner(_out, this);
|
||||
InputStream in = _socket.getInputStream();
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "before startReading");
|
||||
_reader.startReading();
|
||||
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Before getDate");
|
||||
@ -567,9 +586,14 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
* @throws I2PSessionException if the message is malformed or there is an error writing it out
|
||||
*/
|
||||
void sendMessage(I2CPMessage message) throws I2PSessionException {
|
||||
if (isClosed() || _writer == null)
|
||||
if (isClosed())
|
||||
throw new I2PSessionException("Already closed");
|
||||
_writer.addMessage(message);
|
||||
else if (_queue != null)
|
||||
_queue.offer(message); // internal
|
||||
else if (_writer == null)
|
||||
throw new I2PSessionException("Already closed");
|
||||
else
|
||||
_writer.addMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -581,8 +605,7 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
// Only log as WARN if the router went away
|
||||
int level;
|
||||
String msgpfx;
|
||||
if ((error instanceof EOFException) ||
|
||||
(error.getMessage() != null && error.getMessage().startsWith("Pipe closed"))) {
|
||||
if (error instanceof EOFException) {
|
||||
level = Log.WARN;
|
||||
msgpfx = "Router closed connection: ";
|
||||
} else {
|
||||
@ -647,6 +670,10 @@ abstract class I2PSessionImpl implements I2PSession, I2CPMessageReader.I2CPMessa
|
||||
_reader.stopReading();
|
||||
_reader = null;
|
||||
}
|
||||
if (_queue != null) {
|
||||
// internal
|
||||
_queue.close();
|
||||
}
|
||||
if (_writer != null) {
|
||||
_writer.stopWriting();
|
||||
_writer = null;
|
||||
|
@ -19,6 +19,9 @@ import net.i2p.data.i2cp.DestLookupMessage;
|
||||
import net.i2p.data.i2cp.DestReplyMessage;
|
||||
import net.i2p.data.i2cp.GetBandwidthLimitsMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
import net.i2p.internal.I2CPMessageQueue;
|
||||
import net.i2p.internal.InternalClientManager;
|
||||
import net.i2p.internal.QueuedI2CPMessageReader;
|
||||
import net.i2p.util.I2PThread;
|
||||
import net.i2p.util.InternalSocket;
|
||||
|
||||
@ -72,16 +75,26 @@ class I2PSimpleSession extends I2PSessionImpl2 {
|
||||
notifier.start();
|
||||
|
||||
try {
|
||||
// If we are in the router JVM, connect using the interal pseudo-socket
|
||||
_socket = InternalSocket.getSocket(_hostname, _portNum);
|
||||
_out = _socket.getOutputStream();
|
||||
synchronized (_out) {
|
||||
_out.write(I2PClient.PROTOCOL_BYTE);
|
||||
_out.flush();
|
||||
// If we are in the router JVM, connect using the interal queue
|
||||
if (_context.isRouterContext()) {
|
||||
// _socket, _out, and _writer remain null
|
||||
InternalClientManager mgr = _context.internalClientManager();
|
||||
if (mgr == null)
|
||||
throw new I2PSessionException("Router is not ready for connections");
|
||||
// the following may throw an I2PSessionException
|
||||
_queue = mgr.connect();
|
||||
_reader = new QueuedI2CPMessageReader(_queue, this);
|
||||
} else {
|
||||
_socket = new Socket(_hostname, _portNum);
|
||||
_out = _socket.getOutputStream();
|
||||
synchronized (_out) {
|
||||
_out.write(I2PClient.PROTOCOL_BYTE);
|
||||
_out.flush();
|
||||
}
|
||||
_writer = new ClientWriterRunner(_out, this);
|
||||
InputStream in = _socket.getInputStream();
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
}
|
||||
_writer = new ClientWriterRunner(_out, this);
|
||||
InputStream in = _socket.getInputStream();
|
||||
_reader = new I2CPMessageReader(in, this);
|
||||
_reader.startReading();
|
||||
|
||||
} catch (UnknownHostException uhe) {
|
||||
|
@ -27,11 +27,11 @@ import net.i2p.util.Log;
|
||||
public class I2CPMessageReader {
|
||||
private final static Log _log = new Log(I2CPMessageReader.class);
|
||||
private InputStream _stream;
|
||||
private I2CPMessageEventListener _listener;
|
||||
private I2CPMessageReaderRunner _reader;
|
||||
private Thread _readerThread;
|
||||
protected I2CPMessageEventListener _listener;
|
||||
protected I2CPMessageReaderRunner _reader;
|
||||
protected Thread _readerThread;
|
||||
|
||||
private static volatile long __readerId = 0;
|
||||
protected static volatile long __readerId = 0;
|
||||
|
||||
public I2CPMessageReader(InputStream stream, I2CPMessageEventListener lsnr) {
|
||||
_stream = stream;
|
||||
@ -42,6 +42,14 @@ public class I2CPMessageReader {
|
||||
_readerThread.setName("I2CP Reader " + (++__readerId));
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal extension only. No stream.
|
||||
* @since 0.8.3
|
||||
*/
|
||||
protected I2CPMessageReader(I2CPMessageEventListener lsnr) {
|
||||
setListener(lsnr);
|
||||
}
|
||||
|
||||
public void setListener(I2CPMessageEventListener lsnr) {
|
||||
_listener = lsnr;
|
||||
}
|
||||
@ -114,9 +122,9 @@ public class I2CPMessageReader {
|
||||
public void disconnected(I2CPMessageReader reader);
|
||||
}
|
||||
|
||||
private class I2CPMessageReaderRunner implements Runnable {
|
||||
private volatile boolean _doRun;
|
||||
private volatile boolean _stayAlive;
|
||||
protected class I2CPMessageReaderRunner implements Runnable {
|
||||
protected volatile boolean _doRun;
|
||||
protected volatile boolean _stayAlive;
|
||||
|
||||
public I2CPMessageReaderRunner() {
|
||||
_doRun = true;
|
||||
|
51
core/java/src/net/i2p/internal/I2CPMessageQueue.java
Normal file
51
core/java/src/net/i2p/internal/I2CPMessageQueue.java
Normal file
@ -0,0 +1,51 @@
|
||||
package net.i2p.internal;
|
||||
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
|
||||
/**
|
||||
* Contains the methods to talk to a router or client via I2CP,
|
||||
* when both are in the same JVM.
|
||||
* This interface contains methods to access two queues,
|
||||
* one for transmission and one for receiving.
|
||||
* The methods are identical to those in java.util.concurrent.BlockingQueue.
|
||||
*
|
||||
* Reading may be done in a thread using the QueuedI2CPMessageReader class.
|
||||
* Non-blocking writing may be done directly with offer().
|
||||
*
|
||||
* @author zzz
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public abstract class I2CPMessageQueue {
|
||||
|
||||
/**
|
||||
* Send a message, nonblocking.
|
||||
* @return success (false if no space available)
|
||||
*/
|
||||
public abstract boolean offer(I2CPMessage msg);
|
||||
|
||||
/**
|
||||
* Receive a message, nonblocking.
|
||||
* Unused for now.
|
||||
* @return message or null if none available
|
||||
*/
|
||||
public abstract I2CPMessage poll();
|
||||
|
||||
/**
|
||||
* Send a message, blocking until space is available.
|
||||
* Unused for now.
|
||||
*/
|
||||
public abstract void put(I2CPMessage msg) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Receive a message, blocking until one is available.
|
||||
* @return message
|
||||
*/
|
||||
public abstract I2CPMessage take() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* == offer(new PoisonI2CPMessage());
|
||||
*/
|
||||
public void close() {
|
||||
offer(new PoisonI2CPMessage());
|
||||
}
|
||||
}
|
19
core/java/src/net/i2p/internal/InternalClientManager.java
Normal file
19
core/java/src/net/i2p/internal/InternalClientManager.java
Normal file
@ -0,0 +1,19 @@
|
||||
package net.i2p.internal;
|
||||
|
||||
import net.i2p.client.I2PSessionException;
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
|
||||
/**
|
||||
* A manager for the in-JVM I2CP message interface
|
||||
*
|
||||
* @author zzz
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public interface InternalClientManager {
|
||||
|
||||
/**
|
||||
* Connect to the router, receiving a message queue to talk to the router with.
|
||||
* @throws I2PSessionException if the router isn't ready
|
||||
*/
|
||||
public I2CPMessageQueue connect() throws I2PSessionException;
|
||||
}
|
58
core/java/src/net/i2p/internal/PoisonI2CPMessage.java
Normal file
58
core/java/src/net/i2p/internal/PoisonI2CPMessage.java
Normal file
@ -0,0 +1,58 @@
|
||||
package net.i2p.internal;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import net.i2p.data.i2cp.I2CPMessageException;
|
||||
import net.i2p.data.i2cp.I2CPMessageImpl;
|
||||
|
||||
/**
|
||||
* For marking end-of-queues in a standard manner.
|
||||
* Don't actually send it.
|
||||
*
|
||||
* @author zzz
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public class PoisonI2CPMessage extends I2CPMessageImpl {
|
||||
public final static int MESSAGE_TYPE = 999999;
|
||||
|
||||
public PoisonI2CPMessage() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated don't do this
|
||||
* @throws I2CPMessageException always
|
||||
*/
|
||||
protected void doReadMessage(InputStream in, int size) throws I2CPMessageException {
|
||||
throw new I2CPMessageException("Don't do this");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated don't do this
|
||||
* @throws I2CPMessageException always
|
||||
*/
|
||||
protected byte[] doWriteMessage() throws I2CPMessageException {
|
||||
throw new I2CPMessageException("Don't do this");
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return MESSAGE_TYPE;
|
||||
}
|
||||
|
||||
/* FIXME missing hashCode() method FIXME */
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if ((object != null) && (object instanceof PoisonI2CPMessage)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("[PoisonMessage]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
55
core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java
Normal file
55
core/java/src/net/i2p/internal/QueuedI2CPMessageReader.java
Normal file
@ -0,0 +1,55 @@
|
||||
package net.i2p.internal;
|
||||
|
||||
import net.i2p.data.i2cp.I2CPMessage;
|
||||
import net.i2p.data.i2cp.I2CPMessageReader;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
/**
|
||||
* Get messages off an In-JVM queue, zero-copy
|
||||
*
|
||||
* @author zzz
|
||||
* @since 0.8.3
|
||||
*/
|
||||
public class QueuedI2CPMessageReader extends I2CPMessageReader {
|
||||
private final I2CPMessageQueue in;
|
||||
|
||||
public QueuedI2CPMessageReader(I2CPMessageQueue in, I2CPMessageEventListener lsnr) {
|
||||
super(lsnr);
|
||||
this.in = in;
|
||||
_reader = new QueuedI2CPMessageReaderRunner();
|
||||
_readerThread = new I2PThread(_reader, "I2CP Internal Reader " + (++__readerId), true);
|
||||
}
|
||||
|
||||
protected class QueuedI2CPMessageReaderRunner extends I2CPMessageReaderRunner implements Runnable {
|
||||
|
||||
public QueuedI2CPMessageReaderRunner() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (_stayAlive) {
|
||||
while (_doRun) {
|
||||
// do read
|
||||
I2CPMessage msg = null;
|
||||
try {
|
||||
msg = in.take();
|
||||
if (msg.getType() == PoisonI2CPMessage.MESSAGE_TYPE)
|
||||
cancelRunner();
|
||||
else
|
||||
_listener.messageReceived(QueuedI2CPMessageReader.this, msg);
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
if (!_doRun) {
|
||||
// pause .5 secs when we're paused
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException ie) {
|
||||
_listener.disconnected(QueuedI2CPMessageReader.this);
|
||||
cancelRunner();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
core/java/src/net/i2p/internal/package.html
Normal file
7
core/java/src/net/i2p/internal/package.html
Normal file
@ -0,0 +1,7 @@
|
||||
<html><body>
|
||||
<p>
|
||||
Interface and classes for a router and client
|
||||
within the same JVM to directly pass I2CP messages using Queues
|
||||
instead of serialized messages over socket streams.
|
||||
</p>
|
||||
</body></html>
|
Reference in New Issue
Block a user