使用ServerSockets和Sockets时,BufferedReader挂起,看起来连接没有正确形成

时间:2014-06-30 19:53:22

标签: java sockets bufferedreader serversocket

编辑:我已通过在服务器代码中添加一行来更正代码中的错误

我试图编写一些套接字代码,允许我将数据从一台计算机发送到另一台计算机(为了简单起见,我们可以将其视为井字游戏,没有太多的数据需要发送,只有几个数字)。为了实现这一目标,我写了两个类ServerClient。目前我正在使用端口1234通过localhost进行测试,而我只使用该程序的一个单一实例(尽管在尝试使用两个实例时会出现同样的问题)。

首先是这里的代码,然后我可以更深入地了解这个问题,以及我为尝试解决出现问题所采取的测试:

public class Server
{
    private ServerSocket server;
    private Socket socket;

    private Client socketHandler;

    private static final int DEFAULT_PORT = 1234;

    public Server() { this(DEFAULT_PORT); }
    public Server(int port)
    {
        Thread thread = new Thread()
        {
            public void run()
            {
                try
                {
                    System.out.println("Attempting to Establish Connection");
                    server = new ServerSocket(port);
                    socket = server.accept();
                    socketHandler = new Client(port, socket); //THIS LINE ADDED
                    System.out.println("Server Online!");
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        };

        thread.setDaemon(true);
        thread.start();
    }

    //ADJUSTED
    Client getSocketHandler()
    {
        return socketHandler;
    }

    public void kill()
    {
        try
        {
            if (socket != null) socket.close();
            if (server != null) server.close();
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            socket = null;
            server = null;
        }
    }
}

public class Client
{
    public static final int DEFAULT_PORT = 1234;
    public static final String DEFAULT_HOST = "localhost";
    private static final String THUMP_THUMP = "thump thump";
    private static final int PULSE = 1000;

    private int port;
    private String ip;

    private Socket socket;
    private BufferedReader input = null;
    private PrintWriter output = null;

    boolean closed = true;

    String data = "";

    public Client() { this(DEFAULT_PORT, DEFAULT_HOST, null); }
    public Client(int port) { this(port, DEFAULT_HOST, null); }
    public Client(int port, String ip) { this(port, ip, null); }
    public Client(int port, Socket server) { this(port, DEFAULT_HOST, server); }
    public Client(String ip) { this(DEFAULT_PORT, ip, null); }
    public Client(String ip, Socket server) { this(DEFAULT_PORT, ip, server); }
    public Client(Socket server) { this(DEFAULT_PORT, DEFAULT_HOST, server); }
    public Client(int port, String ip, Socket server)
    {
        socket = server;
        this.ip = ip;
        this.port = port;

        Thread thread = new Thread()
        {
            public void run()
            {                
                try
                {
                    initialise(server);
                    String line;
                    startHeartbeat();
                    while (isClosed()) {} //first it is closed, lets wait for it to open before we start waiting for it to close!
                    System.out.println("We are about to listen!");
                    while (!isClosed())
                    {
                        System.out.println("pre-read"); //this line was used to determine that the code was hanging on the next line
                        line = input.readLine(); //offending line
                        System.out.println("post-read"); //this line was used to determine when the block was lifted
                        if (line != null)// || line != THUMP_THUMP)
                        {
                            System.out.println(line);
                            data += line + "\n";
                        }
                    }
                    System.out.println(data);
                    kill();
                    System.out.println("Connection Closed!");
                }
                catch (SocketException e)
                {
                    e.printStackTrace();
                    System.out.println("Server closed!");
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        };

        thread.setDaemon(true);
        thread.start();
    }

    private void initialise(Socket server)
    {
        try
        {
            if (server == null) socket = new Socket(ip, port);
            input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            output = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        }
        catch (IOException e) { e.printStackTrace(); }
    }

    public boolean post(String text)
    {
        synchronized(this)
        {
            output.println(text);
            output.flush();
            return !output.checkError();
        }
    }

    public void kill()
    {
        try
        {
            if (input != null) input.close();
            if (socket != null) socket.close();
        }
        catch(IOException e) { e.printStackTrace(); }
        finally
        {
            input = null;
            socket = null;
        }
    }

    public void killOutputStream()
    {
        try
        {
            if (output != null) output.close();
        }
        catch (Exception e) { e.printStackTrace(); }
        finally
        {
            output = null;
        }
    }

    //////////////////////////////////
    ///////// Socket Control /////////
    //////////////////////////////////

    synchronized boolean isClosed()
    {
        return closed;
    }

    synchronized void setClosed(boolean b)
    {
        closed = b;
    }

    //We need to make sure that the socket is still online, to ensure the reading stops when the connection closes.
    void startHeartbeat()
    {
        Thread heartbeat = new Thread()
        {
            public void run()
            {
                while (output != null)
                {
                    setClosed(post(THUMP_THUMP) ? false : true); //post returns true on success
                    synchronized(this)
                    {
                        try
                        {
                            this.wait(PULSE);
                        }
                        catch (InterruptedException e) {}
                    }
                }
                setClosed(true);
            }
        };
        heartbeat.setDaemon(true);
        heartbeat.start();
    }
}

问题

当客户端启动时(创建服务器之后),它无法读取通过(甚至是心跳)发送的任何数据,实际上代码在读取线程中没有经过line = input.readLine()(这是从现在开始称为违规行),除非看起来如此,直到服务器断开连接(见下文)。

以下是常规测试的顺序:

调用

Server(),然后将生成的Server存储在serverConnection变量中 系统会调用Client(serverConnection != null ? serverConnection.getSocket() : null),新的Client会存储在clientConnection

因为我们可以使用心跳来测试它是否正常工作,所以不需要发送其他数据,并且在经过一段时间后调用serverConnection.kill()然后调用clientConnection.killOutputStream()来终止服务器。

这就是结果:

Attempting to Establish Connection Server Online! 
We are about to listen!

Connection Closed!

其中空行表示在连接过程中收到的非空数据,即没有。

我希望如此:

Attempting to Establish Connection
Server Online!
We are about to listen!
thump thump
thump thump
thump thump (and so on, every second)
Connection closed!

我花了一些时间通过注释或使用相同的测试格式稍微更改代码来执行不同的测试(特殊情况除外,这是6号)并进行了以下观察:

观察

  1. 仅当套接字关闭且输出流关闭时,程序才会移过违规行。
  2. readline()方法开始处理时(心跳切断之前不久),它在流中检测不到任何内容,甚至THUMP_THUMP都没有。
  3. 当套接字关闭但输出流不是时,readline()方法开始处理,只检测不到,心跳将其关闭。没有SocketException,即使它是预期的。
  4. 如果未关闭套接字,并且仅关闭输出流,则会触发SocketException,表示套接字已关闭。
  5. 我在命令提示符中使用了netstat -an,当服务器启动时,端口1234正在侦听。当客户端连接时,它仍然是LISTENING,暗示没有连接。
  6. 我设置了一些python代码通过端口1234连接到自己, 但是我在python代码中犯了一个错误,就像服务器一样 没有关闭,仍然开放。所以我决定连接java 客户端到服务器,看看会发生什么。我是通过跑步来做到的 Client(null)这是非主机的客户端代码。它 导致端口读取ESTABLISHED,而python服务器是 回复"重击",java代码成功 阅读它。没有悬挂,它工作得很好。
  7. 这让我相信问题在于服务器代码,因为python服务器能够与Java客户端成功通信,但Java客户端无法与Java服务器通信。

    在执行此测试之前,我一直专注于客户端代码,认为 it 有错。我在这里发现的具有类似症状的所有问题(参见hereherehere等)对我来说都是空白的,已经在他们的解决方案中写了(大多数是由于输出流没有刷新,或者没有提到,我没有做错,或解决方案没有解决我的问题,所以在这种情况下已被删除以支持心跳)。我最初的代码是基于this文章。

    在试图找出这个问题4天后,我不知所措......我在这里想念的是什么?为什么服务器代码不能像我期望的那样工作?如果有人需要对我的代码进行更多说明,请询问!

    作为后续注释,测试代码是通过一个用javafx编写的简单简约GUI(不是fxml)来运行的,无论这是不是问题我不确定,我认为不会,因为它与Python服务器一起工作。此代码在Java 8中编译

1 个答案:

答案 0 :(得分:2)

考虑到服务器端没有处理输入/输出,我觉得为什么你认为它会比input.readLine()更加困惑....

客户端/服务器连接就像一场网球比赛,因为一方必须接受球,然后再送回球(可能有不同的信息)。您的服务器端必须处理它从start heartbeat方法接收的输入,然后向您发回响应。 input.readLine()函数阻塞线程,直到它从另一端接收数据,所以是的,代码停在那里等待你的服务器发回“网球”。在服务器类中,您应该添加一个处理心跳输入的输入和输出流,并将一串数据发送回客户端。

服务器:

OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();
String response = "thump thump";

while(true){
    is.read();
    os.write(response.getBytes());
    os.flush();
}

使用此示例,客户端应保持不变,只需将上述代码添加到服务器即可。