OutputStreamWriter doesn't seem to obey flush()

I'm writing characters to a BufferedWriter whose underlying OutputStream is a TCP socket connection. At the other end of the socket, I see each full buffer of data, but I don't see the last (partial) buffer of data. On the client side, I verify with debug code that I'm writing all the data to the stream, and then I flush the BufferedWriter and all its underlying output streams. But the last partial buffer of data never makes it to the other end of the connection.

here's my client code:

int i = 0;

char[] buf =newchar[1024];

oos =new ObjectOutputStream(tcpSock.getOutputStream());

<some code that reads and writes objects on oos>

out =new BuffereredWriter(new OutputStreamWriter(OOS));

while((i=in.read(buf))!=-1){

out.write(buf, 0, i);

System.err.println("Client: wrote " + i +" chars");

}

out.flush();

oos.flush();

tcpSock.getOutputStream().flush();

server code:

byte[] buf =newbyte[1024];

int i = 0;

long bytesRead = 0;

<verify that fileSize = number of bytes sent by client>

ois =new ObjectInputStream(tcpConnection.getInputStream());

while((i=ois.read(buf))!=-1 && bytesRead + i < fileSize ){

fos.write(buf, 0, i);

bytesRead = bytesRead + i;

System.err.println("Server: bytes read = " + bytesRead);

}

fos.flush();

output shows that only 1024 bytes are read, and the output file only has 1024 characters.

I have a different method on the client that does the same exact thing except it transfers bytes out an OutputStream rather than chars out a BufferedWriter/OutputStreamWriter, and it does not have this problem. The last partial buffer is read by the server in this other method.

[2478 byte] By [MidnightJavaa] at [2007-11-26 17:52:29]
# 1
But you're writing chars and reading bytes!If the data being transferred isn't known to be 100% text you should be writing and reading bytes only.
ejpa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 2

The data is coming from a Transferable object taken from a Clipboard, using DataFlavor stringFlavor, so it is certainly text. Here's the code that defines object "in"

Clipboard sysCB = Toolkit.getDefaultToolkit().getSystemClipboard();

Transferable contents = sysCB.getContents(null);

in = (StringReader)DataFlavor.stringFlavor.getReaderForText(contents);

It's interesting that it works for every buffer except the last one, and I found this behavior when I pasted in varying amounts of text. And it works for my other method that streams bytes to to an OutputStream. I neglected to mention one other difference between the methods. The one that works takes input from an InputStream and the one that doesn't takes input from the StringReader mentioned above.It seems this (or the different output stream types) is the salient difference rather than using chars vs. bytes (because it works for every buffer except the last).

Message was edited by:

MidnightJava

MidnightJavaa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 3

It turns out this failure is occurring under much simpler conditions, with a byte[] to byte[] transfer over an ObjectOutputSream to ObjectInputStream connection. I created a self-contained test program that replicates the problem.

The code below includes a client and server, communicating on port 8090. The client reads a file called "test_in.txt" from the directory where the program is executing and streams the file to the server. The server then streams it to the file "test_out.txt" in the same directory .

I captured the TCP segments and saw that all data from the file was going to the server. But the server code does not read the last partial buffer of data. I believe the problem is with checking for end of file at the server. Since objects are transferred over the same connection before and after the file is streamed, the client sends the file size and the server is supposed to stop reading after all expected bytes are read. There is something wrong with the looping condition I'm using to do this, but I can't figure out why it doesn't work as coded.

This test needs to be run with more than 1024 bytes in the file "test_in.txt", to re-create the problem. You can download the test file I used from http://mysite.verizon.net/midnightjava/test.html. When the file is copied as "test_out.txt", if the last thing you see is not a quote from Forest Gump, the last buffer was not read.

import java.io.File;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.net.UnknownHostException;

public class FileCopyTest {

int port = 8090;

InetAddress ip = null;

public static void main (String[] args){

FileCopyTest test = new FileCopyTest();

Server server = test.new Server();

server.start();

Client client = test.new Client();

client.start();

}

class Client extends Thread{

public void run(){

FileInputStream fis = null;

File testFile = new File("test_in.txt");

Long fileSize = new Long(testFile.length());

ObjectOutputStream oos = null;

ObjectInputStream ois = null;

Socket sock = new Socket();

String s = "", s2 = "";

System.out.println("Client: file size = " + fileSize);

try {

fis = new FileInputStream(testFile);

} catch (FileNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

try {

ip = InetAddress.getByName("localhost");

} catch (UnknownHostException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

try {

sock = new Socket(ip, port);

oos = new ObjectOutputStream(sock.getOutputStream());

ois = new ObjectInputStream(sock.getInputStream());

oos.writeObject(fileSize);

s = (String) ois.readObject();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

int i = 0;

byte[] buf = new byte[1024];

long bytesWritten = 0;

try {

while((i=fis.read(buf))!=-1) {

oos.write(buf, 0, i);

bytesWritten = bytesWritten + i;

System.err.println("Client: writing " + i + " bytes");

System.err.println("Client: total bytes written: " + bytesWritten);

}

s2 = (String) ois.readObject();

fis.close();

oos.flush();

sock.getOutputStream().flush();

oos.close();

ois.close();

sock.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println("Client received: " + s + " and " + s2);

}

}

class Server extends Thread{

public void run(){

ObjectOutputStream oos = null;

ObjectInputStream ois = null;

FileOutputStream fos = null;

Long fileSize = null;

try {

ServerSocket server = new ServerSocket(port);

Socket sock = server.accept();

oos = new ObjectOutputStream(sock.getOutputStream());

ois = new ObjectInputStream(sock.getInputStream());

fileSize = (Long) ois.readObject();

oos.writeObject(new String("received file size"));

System.out.println("Server: recevied file size: " + fileSize.longValue());

fos = new FileOutputStream("test_out.txt");

byte[] buf = new byte[1024];

int i = 0;

long bytesRead = 0;

do {

i = ois.read(buf);

fos.write(buf, 0, i);

bytesRead = bytesRead + i;

System.err.println("Server: writing " + i + " bytes");

System.err.println("Server: total bytes written: " + bytesRead);

} while ( bytesRead <= fileSize.longValue());

fos.close();

oos.writeObject(new String("Received file"));

oos.close();

ois.close();

sock.close();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (ClassNotFoundException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

MidnightJavaa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 4

Your termination conditions are wrong in both cases. In the last example,

} while ( bytesRead <= fileSize.longValue());

should be

} while ( bytesRead < fileSize.longValue());

Conversely, your first version should iterate reading while (bytesRead+i <= fileSize.longValue()).

ejpa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 5

> should be

> > } while ( bytesRead < fileSize.longValue());

>

Thanks, I tried that and various other permutations. When I try

} while ( bytesRead < fileSize.longValue() );

the program hangs on the line

i = ois.read(buf);

apparently when trying to read the last (partial) buffer.

I think the loop condition you proposed is correct, but the last read operation is blocking forever for some reason. Do you get the same result I do when you run my test program? I've tried it with jre 1.4 and 1.5 FWIW.

MidnightJavaa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 6

I found the problem.

I had this in the client code for the test program

while((i=fis.read(buf))!=-1) {

oos.write(buf, 0, i);

bytesWritten = bytesWritten + i;

System.err.println("Client: writing " + i + " bytes");

System.err.println("Client: total bytes written: " + bytesWritten);

}

s2 = (String) ois.readObject();

fis.close();

oos.flush();

Apparently It's a mistake to read from the input stream before flushing the output stream. So I reversed the order of s2 = (String) ois.readObject();

and oos.flush();

and it worked fine.

If anyone can educate me on this, I'd appreciate knowing why I needed to make this change. I had a previous problem where I closed the client-to-server stream and then I got a SocketException when I tried to read data from the server-to-client stream, even though the server-to-client stream was not closed explicitly. So there are apparently dependencies and interactions between the two streams in either direction on a socket that I don't understand.

MidnightJavaa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 7

> Apparently It's a mistake to read from the input

> stream before flushing the output stream.

Always. What you read from the peer will depend on what you last wrote to the peer, if anything, so the flush() is essential, otherwise the peer is still blocked in its read and you have a deadlock. As a matter of fact, in network programming, before a read is about the only place where fluhing is essential.

ejpa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 8

> the flush() is essential, otherwise the peer is still

> blocked in its read and you have a deadlock.

Thanks for the explanation. That makes sense if the peer is reading and writing to the stream from a single thread. I could have written the server to read with one thread and write with another. Is that considered bad programming practice?

I know TCP better than I know java.net and network coding in java, and I see that I get tripped up assuming that certain TCP behaviors are exposed in the java API when the documentation is silent on them. So TCP supports a half-close, but I am surprised to find that if I close the outbound connection to the peer the inbound connection goes away also. And now I see that outbound and inbound streams are not independent of each other as are TCP segments flowing in opposite directions on a connection.

Obviously, code implementation considerations are bound to impose constraints on the underlying TCP behavior. But it seems to me unfortunate that the dependencies I discovered here are not documented in the API, and I'm not sure I see the rationale for one or both of them (depending on whether it's appropriate to use separate read and write threads). You're a networking expert and I'm not, so I'd like to hear your assessment. Are my concerns valid, or are there other relevant considerations I'm not dealing with?

MidnightJavaa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 9
Yep, closing any stream of a socket closes the other stream and a socket, so that's not a half-close. However TCP half-closes are available via Socket.shutdownInput() and Socket.shutdownOutput().
ejpa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...
# 10

> However TCP half-closes are available via

> Socket.shutdownInput() and Socket.shutdownOutput().

I see. That's helpful. Also I realized ths morning that the other problem I was musing about is not a feature of the JDK at all, just my programming model. Even with separate threads reading and writing at the server, I would still need some sort of inter-process communication to tell the writer that the reader was done, so the former could write the next message. So from the client's perspective the server would be blocking, waiting for the client's last write to complete, and I would have the same deadlock that manifested with the single-threaded server. I assume if I had truly independent streams in my programming model (including separate read/write threads), I would not see this blocking behavior.

Thanks very much for your help.

MidnightJavaa at 2007-7-9 5:05:05 > top of Java-index,Core,Core APIs...