【问题标题】:When using ServerSockets and Sockets, the BufferedReader hangs and it appears the connection doesn't form properly使用 ServerSockets 和 Sockets 时,BufferedReader 挂起并且似乎连接没有正确形成
【发布时间】:2014-06-30 19:53:22
【问题描述】:

编辑:我已经纠正了以下代码中的错误,在服务器代码中添加了一行

我正在尝试编写一些套接字代码,允许我将数据从一台计算机发送到另一台计算机以进行游戏(为简单起见,我们可以将其视为井字游戏,不需要太多数据发送,只是几个数字)。为了实现这一点,我编写了两个类,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()(从现在开始) on 称为违规线路),除非看起来,直到服务器断开连接(见下文)。

以下是常规测试的顺序:

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. 当socket关闭但输出流没有关闭时,readline()方法开始处理,结果什么也没检测到,heartbeat将其切断。没有SocketException,尽管这是意料之中的。
  4. 如果套接字没有关闭,只有输出流关闭,则触发SocketException,提示套接字已关闭。
  5. 我在命令提示符中使用了netstat -an,当服务器启动时,端口 1234 正在监听。客户端连接时,仍然是 LISTENING,表示没有连接。
  6. 我设置了一些 python 代码通过端口 1234 连接到自身, 但是我在python代码中犯了一个错误,因此服务器 没有关闭,仍然是开放的。所以我决定连接java 客户端到服务器,看看会发生什么。我通过跑步做到了这一点 Client(null) 这是非主机的客户端代码。它 导致端口读取 ESTABLISHED,python 服务器是 回响“thump thump”,java代码成功 阅读它。不用挂机,效果很好。

这让我相信问题出在服务器代码上,因为 python 服务器能够与 Java 客户端成功通信,但 Java 客户端无法与 Java 服务器通信。

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

在尝试解决这个问题 4 天后,我不知道该怎么办......我在这里错过了什么?为什么服务器代码没有像我预期的那样工作?如果有人需要对我的代码进行更多说明,请询问!

作为后记,测试代码是通过一个用 javafx(虽然不是 fxml)编写的简单的简约 GUI 运行的,我确定这是否会出现问题,我认为不会,因为它可以工作使用 Python 服务器。这段代码是用 Java 8 编译的

【问题讨论】:

  • 对不起,但你的代码看起来很混乱,首先你没有从服务器向客户端发送任何数据,服务器也没有从客户端读取任何数据。我建议你按照这个基本的例子docs.oracle.com/javase/tutorial/networking/sockets/…,你也不需要一个线程来启动服务器端的套接字,你只需要创建一个新线程来处理接受的连接
  • 服务器 确实 发送数据,主机程序的服务器拥有它自己的客户端,通过将您从ServerSocket.accept() 获得的Socket 传递给客户端...另一个客户端程序将没有服务器,因此将为 ip 和端口创建一个新的Socket
  • 没有。接受连接并不构成发送数据。您在此处发布的服务器根本不会发送任何数据。如果这不是真正的代码,那么在这里发布它是没有意义的。贴出真实代码。
  • 这是真正的代码。测试过程中的位解释了主机客户端是使用以下代码唯一设置的:Client(serverConnection != null ? serverConnection.getSocket() : null) 其中 getSocket() 返回从 accept() 返回的套接字,如上所示。但我确实明白你的意思,与下面的答案配对,只有一侧(在这种情况下是服务器)正在发送和接收数据......测试中没有客户端。
  • 在这种情况下,客户端正在接收数据。服务器除了接受连接外什么都不做。所以客户端阻塞。你说没有客户也是无稽之谈。您似乎不了解自己的代码。

标签: java sockets bufferedreader serversocket


【解决方案1】:

考虑到服务器端没有处理输入/输出,我有点困惑为什么你认为它会比 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();
}

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

【讨论】:

  • 但是为什么客户端没有接收到它发出的心跳呢?只有一种方式吗?
  • @J_mie6 客户端将心跳发送给服务器,它不会发送给自己......服务器应该读取心跳,并通过其输出流提供某种类型的响应。跨度>
  • 对,我明白了……但是……当程序在 Host 程序上设置 Client 类时,它传递从 ServerSocket.accept() 获得的套接字而不是自己的套接字,所以客户端基本上是服务器的套接字。这没有区别吗?
  • 无论如何,明天早上我将尝试您的解决方案,届时我有时间适当地专注于实施它。这已经是漫长的 4 天了哈哈
  • 是的,在上面的 cmets 中我看到了我做错了什么,我已将我认为可以工作的代码添加到服务器代码中,因为我最初打算让它工作。因此,当服务器连接到客户端时,它会将其套接字传递给客户端对象,该对象为其处理输入和输出!我只是按错误的顺序设置它!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-26
  • 1970-01-01
  • 2017-09-17
  • 2014-04-18
  • 1970-01-01
  • 2020-11-23
相关资源
最近更新 更多