Java-对全双工套接字输出流阻塞的写调用
我正在编写一个客户机-服务器应用程序,我想从两个不同的线程(一个线程用于读取,一个线程用于写入)在一个套接字上读写。我的系统几乎可以正常工作了,但有一个令人困惑的错误,我似乎无法理解。读和写彼此独立地工作,但是当我开始在一个线程中从Java-对全双工套接字输出流阻塞的写调用,java,sockets,network-programming,nio,socketchannel,Java,Sockets,Network Programming,Nio,Socketchannel,我正在编写一个客户机-服务器应用程序,我想从两个不同的线程(一个线程用于读取,一个线程用于写入)在一个套接字上读写。我的系统几乎可以正常工作了,但有一个令人困惑的错误,我似乎无法理解。读和写彼此独立地工作,但是当我开始在一个线程中从套接字的输出流读取时,所有对输入流的写入调用都会无限期地在不同的线程块中进行 我已经编写了一个小测试程序,用于快速重现问题,并尽可能多地消除外部变量。我使用java.nio服务器SocketChannel和SocketChannel来建立连接,我使用java.io的S
套接字的输出流
读取时,所有对输入流
的写入调用都会无限期地在不同的线程块中进行
我已经编写了一个小测试程序,用于快速重现问题,并尽可能多地消除外部变量。我使用java.nio
服务器SocketChannel
和SocketChannel
来建立连接,我使用java.io
的Socket
(一个SocketChannel
的底层套接字)来方便使用ObjectInputStream
和ObjectOutputStream
。测试程序设计为运行两次;第一次运行时,用户输入s
启动服务器,第二次运行时,用户输入c
运行客户端
我的问题是:为什么在第二次调用objectOutput.writeObject(message)时执行下面的程序块在server()
方法中的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;
}
}
}
服务器
预期产出:
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可能不支持从不同线程同时读写,但我对堆栈溢出的搜索表明它们确实支持这种同时操作(全双工)。这是我对上述代码的操作感到困惑的主要原因
客户
预期产出:
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
这将确认客户端已成功发送和接收第一个对象。由于服务器中的这种奇怪的阻塞行为,客户端似乎正在等待服务器从未发送的第二个对象
提前非常感谢任何人提供的任何建议。如果全双工很容易以另一种方式实现,我愿意重写我的代码,但如果有一种使用上述结构的解决方案,我更愿意坚持这样做,因为不必重构大量代码。这段代码有很多错误,我将不得不逐行处理:
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();
上面没有“初始化连接”的内容。客户端初始化连接。这段代码接受它
这是默认设置。您不需要断言默认值
while ( !socketChannel.finishConnect() )
Thread.sleep( 100 );
你不应该叫这个finishConnect()
适用于在非阻塞模式下调用了connect()
的客户端。您是一个服务器,没有调用connect()
,也没有处于非阻塞模式。如果您是处于非阻塞模式的客户机,则不应在带有睡眠的循环中调用它:您应使用Selector.select()
和OP\u CONNECT
Socket socket = socketChannel.socket();
ObjectOutput objectOutput = new ObjectOutputStream( socket.getOutputStream() );
当您使用阻塞模式和输出流时,根本不可能理解为什么要使用ServerSocketChannel
和SocketChannel
,事实上,这至少是问题的一部分。一个鲜为人知的事实是,从NIO通道派生的流在通道上使用同步进行读写,因此它们根本不是全双工的,即使底层TCP连接是全双工的。删除所有这些内容并使用ServerSocket
和Socket
重写
// 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();
}
不要这样写代码。取决于前面的try
块成功与否的如下代码必须位于该try
块内。否则,例如,以下代码可能会获得NullPointerExceptions
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" );
}
请参见上文,了解为什么在独立线程中执行此操作无法在从NIO通道派生的流的情况下工作
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" );
}
您可以按原样使用其余部分,但NIO通道在这里同样毫无意义。您也可以使用Socket
如果有帮助的话,我正在Eclipse中运行Java9!感谢所有的反馈。这一切都很有道理。我最初开始使用nio是因为我要手动写入和读取字节。在做出选择之后,为了便于使用对象流,我又回到了普通套接字。关于从同步的SocketChannel创建的套接字中产生的流的部分正是我想要的。再次感谢!我可以证实。我做了你答案中建议的所有更改,我的代码运行完美无瑕。有朝一日我可能会回到nio,了解他们的选择器和其他特性,但对于这个项目,这非常有效。
// 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();
}