7

I have build a simple application that opens a ServerSocket, and on connection, it connects itself to another server socket on a remote machine. To implement port forwarding, I use two threads, one that reads from the local inputstream and streams to the remote sockets outputstream, and vice versa.

The implementation feels a bit inperformant, and so I ask you if you know a better implementation strategy, or even have some code lying around to achive this in a performant way.

PS: I know I could use IPTables on Linux, but this has to work on Windows.

PPS: If you post implementations for this simple task, I will create a benchmark to test all given implementations. The solution should be fast for many small (~100bytes) packages and steady data streams.

My current implementation is this (executed on each of the two threads for each direction):

public static void route(InputStream inputStream, OutputStream outputStream) throws IOException {
    byte[] buffer = new byte[65536];
    while( true ) {
        // Read one byte to block
        int b = inputStream.read();
        if( b == - 1 ) {
            log.info("No data available anymore. Closing stream.");
            inputStream.close();
            outputStream.close();
            return;
        }
        buffer[0] = (byte)b;
        // Read remaining available bytes
        b = inputStream.read(buffer, 1, Math.min(inputStream.available(), 65535));
        if( b == - 1 ) {
            log.info("No data available anymore. Closing stream.");
            inputStream.close();
            outputStream.close();
            return;
        }
        outputStream.write(buffer, 0, b+1);
    }
}
Daniel
  • 25,883
  • 17
  • 87
  • 130
  • 3
    Rather than a "feeling", can you measure and elaborate on what's wrong with this implementation - latency? throughput? system load? – SimonJ Oct 17 '10 at 17:54
  • I connected to an SSH server, an my implementation was very laggy. But I already found out that I had (by using some redirection ports) the same traffic going over the wire 5 times, instead of 1 time which I was comparing to. Selfslap. However, the question still holds: Can I make the above faster? – Daniel Oct 17 '10 at 18:33
  • I use JPortForward https://sourceforge.net/projects/jportforward/ which implements this pretty much the same as you do. Unless you have very heavy load I can't imagine you'll be able to make much difference to performance but if you need to make it faster you probably ought to look at the separate Windows and Linux native socket APIs with async callbacks etc. I'd guess that'd be a lot of work for not much gain though. – Rup Oct 18 '10 at 13:10
  • 1
    Another similar question: http://stackoverflow.com/questions/1677248/simple-ssh-tunnel-in-java – Jay Taylor Aug 13 '12 at 21:42

4 Answers4

9

Take a look at tcpmon. Its purpose is to monitor tcp data, but it also forwards to a different host/port.

And here is some code for port forwarding taken from a book (it's not in English, so I'm pasting the code rather than giving a link to the book e-version):

Extreme Coders
  • 3,195
  • 2
  • 33
  • 49
Bozho
  • 554,002
  • 136
  • 1,025
  • 1,121
  • Stupid me... I thought read(byte[]) would fill the byte array first – Daniel Oct 17 '10 at 19:06
  • 2
    Link provided seems to be broken . Github link found in google for the code https://github.com/cartmanez/port-forward/blob/master/src/main/java/ed/port/forward/ or http://www.nakov.com/books/inetjava/source-code-html/Chapter-1-Sockets/1.4-TCP-Sockets/TCPForwardServer.java.html – Mani Jul 21 '16 at 05:20
7

A couple of observations:

  • The one byte read at the start of the loop does nothing to improve performance. Probably the reverse in fact.

  • The call to inputStream.available() is unnecessary. You should just try to read to "buffer size" characters. A read on a Socket streamwill return as many characters as are currently available, but won't block until the buffer is full. (I cannot find anything in the javadocs that says this, but I'm sure it is the case. A lot of things would perform poorly ... or break ... if read blocked until the buffer was full.)

  • As @user479257 points out, you should get better throughput by using java.nio and reading and writing ByteBuffers. This will cut down on the amount of data copying that occurs in the JVM.

  • Your method will leak Socket Streams if a read, write or close operation throws an exception. You should use a try ... finally as follows to ensure that the streams are always closed no matter what happens.


public static void route(InputStream inputStream, OutputStream outputStream) 
throws IOException {
    byte[] buffer = new byte[65536];
    try {
        while( true ) {
            ...
            b = inputStream.read(...);
            if( b == - 1 ) {
                log.info("No data available anymore. Closing stream.");
                return;
            }
            outputStream.write(buffer, 0, b+1);
        }
    } finally {
        try { inputStream.close();} catch (IOException ex) { /* ignore */ }
        try { outputStream.close();} catch (IOException ex) { /* ignore */ }
    }
}
sazzy4o
  • 2,110
  • 4
  • 31
  • 55
Stephen C
  • 632,615
  • 86
  • 730
  • 1,096
  • +1 I already found out about the unnessesary byte at the beginning. But you missed the b+1 at the end that should be just b. Is it faster to read NIO ByteBuffers? Aren't they just ordinary byte arrays if they don't come from disk but from a network interface? – Daniel Oct 18 '10 at 15:01
  • 4
    @Daniel - It is not **my** job to find all of your bugs. :-) – Stephen C Oct 18 '10 at 21:46
0

If your code isn't performant, maybe your buffers aren't large enough.

Too small buffers mean that more request will be done and less performances.


On the same topic :

Community
  • 1
  • 1
Colin Hebert
  • 85,401
  • 13
  • 150
  • 145
0

Did 2 reads and one buffer check per loop iteration really sped things up and have you measured that? Looks like a premature optimization to me... From personal experience, simply reading into a small buffer and then writing it to the output works well enough. Like that: byte[] buf = new byte[1024]; int read = m_is.read(buf); while(read != -1) { m_os.write(buf, 0, read); m_fileOut.write(buf, 0, read); read = m_is.read(buf); } This is from an old proxy of mine that used InputStream.read() in the first version, then went to available() check + 1024 byte buffer in the second and settled on the code above in the third.

If you really really need performance (or just want to learn), use java.nio or one of the libraries that build on it. Do note that IO performance tends to behave wildly different on different platforms.

bod
  • 126
  • 4