views:

531

answers:

4

Hi,

I'm making a program in Java with Sockets. I can send commands to the client and from the client to the server. To read the commands I use a BufferedReader. To write them, a PrintWriter But now I want to transfer a file through that socket (Not simply create a second connection).
First I write to the outputstream how many bytes the file contains. For example 40000 bytes. So I write the number 40000 through the socket, but the other side of the connection reads 78.

So I was thinking: The BufferedReader reads more than just the line (by calling readLine()) and on that way I lose some bytes from the file-data. Because they are in the buffer from the BufferedReader.
So the number 78 is a byte of the file I want to transmit.

Is this way of thinking right, or not. If so, how to sovle this problem.
I hope I've explained well.


Here is my code, but my default language is Dutch. So some variable-name can sound stange.

public void flushStreamToStream(InputStream is, OutputStream os, boolean closeIn, boolean closeOut) throws IOException {
    byte[] buffer = new byte[BUFFERSIZE];
    int bytesRead;
    if ((!closeOut) && closeIn) { // To Socket from File
        action = "Upload";
        os.write(is.available()); // Here I write 400000
        max = is.available();
        System.out.println("Bytes to send: " + max);
        while ((bytesRead = is.read(buffer)) != -1) {
            startTiming(); // Two lines to compute the speed
            os.write(buffer, 0, bytesRead);
            stopTiming(); // Speed compution
            process += bytesRead;
        }
        os.flush();
        is.close();
        return;
    }
    if ((!closeIn) && closeOut) { // To File from Socket
        action = "Download";
        int bytesToRead = -1;
        bytesToRead = is.read(); // Here he reads 78.
        System.out.println("Bytes to read: " + bytesToRead);
        max = bytesToRead;
        int nextBufferSize;
        while ((nextBufferSize = Math.min(BUFFERSIZE, bytesToRead)) > 0) {
            startTiming();
            bytesRead = is.read(buffer, 0, nextBufferSize);
            bytesToRead -= bytesRead;
            process += nextBufferSize;
            os.write(buffer, 0, bytesRead);
            stopTiming();
        }
        os.flush();
        os.close();
        return;
    }
    throw new IllegalArgumentException("The only two boolean combinations are: closeOut == false && closeIn == true AND closeOut == true && closeIn == false");
}


Here is the solution:
Thanks to James suggestion
I think laginimaineb anwser was a piece of the solution.

Read the commands.

DataInputStream in = new DataInputStream(is); // Originally a BufferedReader
// Read the request line
String str;
while ((str = in.readLine()) != null) {
    if (str.trim().equals("")) {
       continue;
    }
    handleSocketInput(str);
}

Now the flushStreamToStream:

public void flushStreamToStream(InputStream is, OutputStream os, boolean closeIn, boolean closeOut) throws IOException {
    byte[] buffer = new byte[BUFFERSIZE];
    int bytesRead;
    if ((!closeOut) && closeIn) { // To Socket from File
        action = "Upload";
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeInt(is.available());

        max = is.available();
        System.out.println("Bytes to send: " + max);
        while ((bytesRead = is.read(buffer)) != -1) {
            startTiming();
            dos.write(buffer, 0, bytesRead);
            stopTiming();
            process += bytesRead;
        }
        os.flush();
        is.close();
        return;
    }
    if ((!closeIn) && closeOut) { // To File from Socket
        action = "Download";
        DataInputStream dis = new DataInputStream(is);
        int bytesToRead = dis.readInt();
        System.out.println("Bytes to read: " + bytesToRead);
        max = bytesToRead;
        int nextBufferSize;
        while ((nextBufferSize = Math.min(BUFFERSIZE, bytesToRead)) > 0) {
            startTiming();
            bytesRead = is.read(buffer, 0, nextBufferSize);
            bytesToRead -= bytesRead;
            process += nextBufferSize;
            os.write(buffer, 0, bytesRead);
            stopTiming();
        }
        os.flush();
        os.close();
        return;
    }
    throw new IllegalArgumentException("The only two boolean combinations are: closeOut == false && closeIn == true AND closeOut == true && closeIn == false");
}

Martijn.

+4  A: 

I'm not sure I've followed your explanation.

However, yes - you have no real control over how much a BufferedReader will actually read. The point of such a reader is that it optimistically reads chunks of the underlying resource as needed to replenish its buffer. So when you first call readLine, it will see that its internal buffer doesn't have enough to serve youthe request, and will go off and read however many bytes it feels like into its buffer from the underlying source, which will generally be much more than you asked for just then. Once the buffer's been populated, it returns your line from the buffered content.

Thus once you wrap an input stream in a BufferedReader, you should be sure to only read that stream through the same buffered reader. If you don't you'll end up losing data (as some bytes will have been consumed and are now sitting in the BufferedReader's cache waiting to be served).

Andrzej Doyle
Right. One thing on my TODO list is a `Reader`/`InputStream` combination that buffers characters *and* delivers bytes. It has been there since 2003, at least. :)
Bombe
This sounds good. But I checked to source-code from `BufferedReader.read();`. Every `\n` char will be skipped. So this means he will skip every 10-byte (10 == (int) '\n')
Martijn Courteaux
A: 

Just a wild stab here - 40000 is 1001110001000000 in binary. Now, the first seven bits here are 1001110 which is 78. Meaning, you're writing 2 bytes of information but reading seven bits.

laginimaineb
`40000` (decimal) is `9C40` (hex).
Bombe
Okay, gotcha, but what I meant is that the first seven bits are 78 (corrected above)
laginimaineb
A: 

A BufferedReader assumes that it is the only one reading from the underlying input stream.

It's purpose is to minimize the number of reads from the underlying stream (which are expensive, as they can delegate quite deeply). To that end, it keeps a buffer, which it fills by reading as many bytes as possible into it in a single call to the underlying stream.

So yes, your diagnosis is accurate.

meriton
+1  A: 

DataInputStream is most likely what you want to use. Also, don't use the available() method as it is generally useless.

james