I'm writing a client server application and I want to both read and write on one socket from two different threads (one thread for reading, one for writing). I have the system nearly working, but there is one perplexing bug that I can't seem to wrap my head around. Reading and writing work perfect independent of each other, but when I start reading from the Socket
's OutputStream
in one thread, all calls to write to the InputStream
in a different thread block indefinitely.
I've written a small test program for quickly reproducing the issue and to eliminate as many external variables as I could. I use java.nio
's ServerSocketChannel
and SocketChannel
to set up the connection and I use java.io
's Socket
(the underlying socket of a SocketChannel
) for its ease of use with ObjectInputStream
and ObjectOutputStream
. The test program is designed to run twice; for the first run, the user inputs s
to launch the server and on the second run the user inputs c
to run the client.
My question is: Why does execution of the below program block on the second call to objectOutput.writeObject( message );
in the server()
method? (fourth to last line in that method)
I've included the expected outputs and actual outputs and what I think they mean below the program code.
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
public class Main {
private static final String IP_ADDRESS = "localhost";
private static final int WELL_KNOWN_PORT = 4000;
public static void main( String... args ) throws Exception {
Scanner scanner = new Scanner( System.in );
System.out.print( "choose (s)erver or (c)lient: " );
char choice = scanner.nextLine().charAt( 0 );
switch ( choice ) {
case 's':
server();
break;
case 'c':
client();
break;
default:
break;
}
scanner.close();
}
private static void server() throws Exception {
// initialize connection
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind( new InetSocketAddress( WELL_KNOWN_PORT ) );
System.out.println( "waiting for client to connect" );
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println( "client connected" );
socketChannel.configureBlocking( true );
while ( !socketChannel.finishConnect() )
Thread.sleep( 100 );
Socket socket = socketChannel.socket();
ObjectOutput objectOutput = new ObjectOutputStream( socket.getOutputStream() );
// write first object to stream
Message message = new Message( 1 );
System.out.println( "writing first object to object output stream: " + message );
objectOutput.writeObject( message );
System.out.println( "first object written to object output stream" );
objectOutput.flush();
System.out.println( "object output stream flushed" );
// start reading in a separate thread
new Thread( () -> {
ObjectInput objectInput = null;
try {
objectInput = new ObjectInputStream( socket.getInputStream() );
} catch ( IOException e ) {
e.printStackTrace();
}
Message messageIn = null;
try {
System.out.println( "reading on object input stream" );
messageIn = (Message) objectInput.readObject();
System.out.println( "read object on object input stream: " + messageIn );
} catch ( ClassNotFoundException | IOException e ) {
e.printStackTrace();
}
System.out.println( messageIn );
} ).start();
Thread.sleep( 100 ); // allow time for object listening to start
// write second object to stream
message = new Message( 2 );
System.out.println( "writing second object to object output stream: " + message );
objectOutput.writeObject( message ); // this call seems to block??
System.out.println( "second object written to object output stream" );
objectOutput.flush();
System.out.println( "object output stream flushed" );
}
private static void client() throws Exception {
// initialize connection
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking( true );
socketChannel.connect( new InetSocketAddress( IP_ADDRESS, WELL_KNOWN_PORT ) );
while ( !socketChannel.finishConnect() )
Thread.sleep( 100 );
Socket socket = socketChannel.socket();
ObjectOutput objectOutput = new ObjectOutputStream( socket.getOutputStream() );
ObjectInput objectInput = new ObjectInputStream( socket.getInputStream() );
// read first object
System.out.println( "reading first object on object input stream" );
Message message = (Message) objectInput.readObject();
System.out.println( "read first object on object input stream: " + message );
// read second object
System.out.println( "reading second object on object input stream" );
message = (Message) objectInput.readObject();
System.out.println( "read second object on object input stream: " + message );
// write confirmation message
message = new Message( 42 );
System.out.println( "writing confirmation message to object output stream: " + message );
objectOutput.writeObject( message );
System.out.println( "confirmation message written to object output stream" );
objectOutput.flush();
System.out.println( "object output stream flushed" );
}
private static class Message implements Serializable {
private static final long serialVersionUID = 5649798518404142034L;
private int data;
public Message( int data ) {
this.data = data;
}
@Override
public String toString() {
return "" + data;
}
}
}
Server
Expected output:
choose (s)erver or (c)lient: s
waiting for client to connect
client connected
writing first object to object output stream: 1
first object written to object output stream
object output stream flushed
reading on object input stream
writing second object to object output stream: 2
second object written to object output stream
object output stream flushed
read object on object input stream: 42
Actual output:
choose (s)erver or (c)lient: s
waiting for client to connect
client connected
writing first object to object output stream: 1
first object written to object output stream
object output stream flushed
reading on object input stream
writing second object to object output stream: 2
The application successfully sends the first object but blocks indefinitely on the second. The only difference I can see is that the second write call happens while a read operation is in process on a separate thread. My first instinct was that maybe Socket
s don't support simultaneous reading and writing from different threads, but my search of Stack Overflow suggests that they do support this simultaneous operation (full duplex). This is the primary reason I'm confused with the operation of the above code.
Client
Expected output:
choose (s)erver or (c)lient: c
reading on object input stream
read first object on object input stream: 1
reading second object on object input stream
read second object on object input stream: 2
writing confirmation message to object output stream: 42
confirmation message written to object output stream
object output stream flushed
Actual output:
choose (s)erver or (c)lient: c
reading first object on object input stream
read first object on object input stream: 1
reading second object on object input stream
This confirms that the first object was successfully sent and received by the client. The client seems to be waiting on the second object which is never sent by the server due to this strange blocking behavior in the server.
Thanks so much in advance for any advice anyone may be able to give. I'm open to rewriting my code if full duplex is easily achievable in another manner, but If there is a solution using the above structure I would much prefer to stick with that for simplicity of not having to refactor large sections of code.