Kazuo
Kazuo

Reputation: 397

Fastest or best way to read from SSLSocket

I'm running a multithreaded minimalistic http(s) server (not a web server though) that accepts connections on three server sockets: local, internet and internet-ssl.

Each socket has a so timeout of 1000ms (might be lowered in the future).

The worker threads read requests like this:

byte[] reqBuffer = new byte[512];
theSocket.getInputStream().read(reqBuffer);

The problem now is that with the newly implemented ssl socket the problem with the 1/n-1 record splitting technique arises. Also some clients split in other strange ways when using ssl (4/n-4 etc.) so I thought I might just perform multiple reads like this:

byte[] reqBuffer = new byte[512];
InputStream is = theSocket.getInputStream();
int read = is.read(reqBuffer, 0, 128); // inital read - with x/n-x this is very small
int pos = 0;
if (read > 0) {
   pos = read;
}
int i = 0;
do {
   read = is.read(reqBuffer, pos, 128);
   if (read > 0) {
      pos += read;
   }
   i++;
} while(read == 128 && i < 3); // max. 3 more reads (4 total = 512 bytes) or until less than 128 bytes are read (request should be completely read)

Which works with browsers like firefox or chrome and other clients using that technique.

Now my problem is that the new method is much slower. Requests to the local socket are so slow that a script with 2 seconds timeout times out requesting (I have no idea why). Maybe I have some logical problem in my code?

Is there a better way to read from a SSL socket? Because there are up to hundreds or even a thousand requests per second and the new read method slows down even the http requests.

Note: The ssl-socket is not in use at the moment and will not be used until I can fix this problem.

I have also tried reading line for line using a buffered reader since we are talking about http here but the server exploded running out of file descriptors (limit is 20 000). Might have been because of my implementation, though.

I'm thankful for every suggestion regarding this problem. If you need more information about the code just tell me and I will post them asap.

EDIT: I actually put a little bit more thought into what I am trying to do and I realized that it comes down to reading HTTP headers. So the best solution would be to actually read the request line for line (or character for character) and stop reading after x lines or until an empty line (marking the end of the header) is reached. My current approach would be to put a BufferedInputStream around the socket's InputStream and read it with an InputStreamReader which is "read" by a BufferedReader (question: does it make sense to use a BufferedInputStream when I'm using a BufferedReader?). This BufferedReader reads the request character for character, detects end-of-lines (\r\n) and continues to read until either a line longer than 64 characters is reached, a maximum of 8 lines are read or an empty line is reached (marking the end of the HTTP header). I will test my implementation tomorrow and edit this edit accordingly.

EDIT: I almost forgot to write my results here: It works. On every socket, even faster than the previously working way. Thanks everyone for pointing me in the right direction. I ended up implementing it like this:

List<String> requestLines = new ArrayList<String>(6);
InputStream is = this.cSocket.getInputStream();
bis = new BufferedInputStream(is, 1024);
InputStreamReader isr = new InputStreamReader(bis, Config.REQUEST_ENCODING);
BufferedReader br = new BufferedReader(isr);

/* read input character for character
* maximum line size is 768 characters
* maximum number of lines is 6
* lines are defined as char sequences ending with \r\n
* read lines are added to a list
* reading stops at the first empty line => HTTP header end
*/
int readChar; // the last read character
int characterCount = 0; // the character count in the line that is currently being read
int lineCount = 0; // the overall line count
char[] charBuffer = new char[768]; // create a character buffer with space for 768 characters (max line size)

// read as long as the stream is not closed / EOF, the character count in the current line is below 768 and the number of lines read is below 6
while((readChar = br.read()) != -1 && characterCount < 768 && lineCount < 6) {
    charBuffer[characterCount] = (char) readChar; // fill the char buffer with the read character
    if (readChar == '\n' && characterCount > 0 && charBuffer[characterCount-1] == '\r') { // if end of line is detected (\r\n)
        if (characterCount == 1) { // if empty line
            break; // stop reading after an empty line (HTTP header ended)
        }
        requestLines.add(new String(charBuffer, 0, characterCount-1)); // add the read line to the readLines list (and leave out the \r)
        // charBuffer = new char[768]; // clear the buffer - not required
        characterCount = 0; // reset character count for next line
        lineCount++; // increase read line count
    } else {
        characterCount++; // if not end of line, increase read character count
    }
}

Upvotes: 2

Views: 3753

Answers (2)

user207421
user207421

Reputation: 311050

You should certainly wrap a BufferedInputStream around the SSLSocket's input stream.

Your technique of reading 128 bytes at a time and advancing the offset is completely pointless. Just read as much as you can at a time and deal with it. Or one byte at a time from the buffered stream.

Similarly you should certainly wrap the SSLSocket's output stream in a BufferedOutputStream.

Upvotes: 3

Peter Lawrey
Peter Lawrey

Reputation: 533870

This is most likely slower as you are waiting for the other end to send more data, possibly data it is never going to send.

A better approach is you give it a larger buffer like 32KB (128 is small) and only read once the data which is available. If this data needs to be re-assembled in the messages of some sort, you shouldn't be using timeouts or a fixed number of loops as read() is only guaranteed to return one byte at least.

Upvotes: 3

Related Questions