简体   繁体   English

Java - 在全双工中写入对套接字输出流阻塞的调用

[英]Java - Write call to socket output stream blocking in full duplex

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.读写工作完全相互独立,但是当我从一个线程中的SocketOutputStream开始读取时,所有在不同线程中写入InputStream调用都会无限期地阻塞。

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 .我使用java.nioServerSocketChannelSocketChannel来建立连接,我使用java.ioSocketSocketChannel的底层套接字)是因为它易于与ObjectInputStreamObjectOutputStream 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.第一次运行时,用户输入s来启动服务器,第二次运行时,用户输入c来运行客户端。

My question is: Why does execution of the below program block on the second call to objectOutput.writeObject( message );我的问题是:为什么在第二次调用objectOutput.writeObject( message );执行以下程序块objectOutput.writeObject( message ); in the server() method?server()方法中? (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).我的第一直觉是,也许Socket不支持从不同线程同时读取和写入,但是我对 Stack Overflow 的搜索表明它们确实支持这种同时操作(全双工)。 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.如果可以通过另一种方式轻松实现全双工,我愿意重写我的代码,但是如果有使用上述结构的解决方案,我更愿意坚持使用它,以便不必重构大部分代码。

There are so many things wrong with this code I will have to take it line by line:这段代码有很多问题,我必须一行一行地理解:

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();

There is nothing above that 'initializes a connection'.上面没有“初始化连接”。 The client initializes the connection.客户端初始化连接。 This code accepts it.此代码接受它。

        System.out.println( "client connected" );
        socketChannel.configureBlocking( true );

This is the default.这是默认设置。 You don't need to assert defaults.您不需要断言默认值。

        while ( !socketChannel.finishConnect() )
            Thread.sleep( 100 );

You shouldn't be calling this.你不应该这样称呼。 finishConnect() is for clients that have called connect() , in non-blocking mode. finishConnect()用于在非阻塞模式下调用connect()客户端 You are a server, you haven't called connect() , and you aren't in non-blocking mode.你是一个服务器,你没有调用connect() ,你没有处于非阻塞模式。 And if you are a client in non-blocking mode you shouldn't call it in a loop with sleeps: you should use Selector.select() with OP_CONNECT .如果您处于非阻塞模式的客户端,则不应在睡眠循环中调用它:您应该将Selector.select()OP_CONNECT

        Socket socket = socketChannel.socket();
        ObjectOutput objectOutput = new ObjectOutputStream( socket.getOutputStream() );

As you are using blocking mode and output streams it is impossible to see why you are using ServerSocketChannel and SocketChannel at all, and as a matter of fact this is at least part of the problem.当您使用阻塞模式和输出流时,根本无法理解为什么要使用ServerSocketChannelSocketChannel ,事实上,这至少是问题的一部分。 It is a little-known fact that streams derived from NIO channels use synchronization on the channel for both reading and writing, and they are therefore not full-duplex at all, even though the underlying TCP connection is.一个鲜为人知的事实是,来自 NIO 通道的流使用通道上的同步进行读取和写入,因此它们根本不是全双工的,即使底层 TCP 连接是。 Remove all this and rewrite using ServerSocket and Socket .删除所有这些并使用ServerSocketSocket重写。

        // 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();
            }

Don't write code like this.不要写这样的代码。 Code like the below that depends on the success of a prior try block like the above must be inside that try block.像下面这样依赖于前面的try块是否成功的代码必须在该try块内。 Otherwise for example the following code is liable to get NullPointerExceptions .否则,例如以下代码可能会得到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();
            }

Ditto.同上。

            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" );
    }

See above for why doing this in separate threads cannot work in the case of streams derived from NIO channels.请参阅上文,了解为什么在从 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 );

The last two lines above are pointless, because the connect has completed, because you're in blocking mode.上面的最后两行毫无意义,因为连接已经完成,因为您处于阻塞模式。

        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" );
    }

You can use the rest of this as-is, but again the NIO channels are pointless here.您可以按原样使用其余部分,但这里的 NIO 通道再次毫无意义。 You may as well use Socket .您也可以使用Socket

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM