Я пишу клиент-серверное приложение, и я хочу читать и писать в один сокет из двух разных потоков (один поток для чтения, один для записи). У меня система почти работает, но есть одна озадачивающая ошибка, которую я никак не могу понять. Чтение и запись работают совершенно независимо друг от друга, но когда я начинаю читать из Socket
из OutputStream
в одном потоке, все вызовы для записи в InputStream
в другом потоке блокируются на неопределенный срок.
Я написал небольшую тестовую программу, чтобы быстро воспроизвести проблему и исключить как можно больше внешних переменных. Я использую ServerSocketChannel
и SocketChannel
java.nio
для установки соединения, и я использую Socket
java.io
(основной сокет SocketChannel
) для простоты использования с ObjectInputStream
и ObjectOutputStream
. Программа тестирования рассчитана на двойной запуск; при первом запуске пользователь вводит s
для запуска сервера, а при втором запуске пользователь вводит c
для запуска клиента.
Мой вопрос: Почему выполнение приведенной ниже программы блокируется при втором вызове objectOutput.writeObject( message );
в методе server()
? (четвертая до последней строки в этом методе)
Я включил ожидаемые результаты и фактические результаты, а также то, что я думаю, что они означают ниже программного кода.
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;
}
}
}
Сервер
Ожидаемый результат:
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
Фактический результат:
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
Приложение успешно отправляет первый объект, но блокирует второй на неопределенный срок. Единственная разница, которую я вижу, заключается в том, что второй вызов записи происходит, когда операция чтения выполняется в отдельном потоке. Сначала я подумал, что, возможно, Socket
s не поддерживают одновременное чтение и запись из разных потоков, но мой поиск в Stack Overflow показывает, что они поддерживают эту одновременную операцию (полный дуплекс). Это основная причина, по которой меня смущает работа приведенного выше кода.
Клиент
Ожидаемый результат:
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
Фактический результат:
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
Это подтверждает, что первый объект был успешно отправлен и получен клиентом. Клиент, кажется, ожидает второго объекта, который никогда не отправляется сервером из-за этого странного поведения блокировки на сервере.
Заранее большое спасибо за любой совет, который может дать кто угодно. Я готов переписать свой код, если полный дуплекс легко достижим другим способом, но если есть решение, использующее вышеуказанную структуру, я бы предпочел придерживаться этого для простоты, поскольку не нужно рефакторить большие участки кода.