【发布时间】:2014-06-30 19:53:22
【问题描述】:
编辑:我已经纠正了以下代码中的错误,在服务器代码中添加了一行
我正在尝试编写一些套接字代码,允许我将数据从一台计算机发送到另一台计算机以进行游戏(为简单起见,我们可以将其视为井字游戏,不需要太多数据发送,只是几个数字)。为了实现这一点,我编写了两个类,Server 和 Client。目前我正在使用端口 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 号)来执行不同的测试,并进行了以下观察:
观察
- 只有当套接字关闭并且输出流关闭时,程序才会越过有问题的行。
- 当
readline()方法开始处理时(在心跳将其切断之前不久),它检测不到流中的任何内容,甚至没有检测到THUMP_THUMP。 - 当socket关闭但输出流没有关闭时,
readline()方法开始处理,结果什么也没检测到,heartbeat将其切断。没有SocketException,尽管这是意料之中的。 - 如果套接字没有关闭,只有输出流关闭,则触发
SocketException,提示套接字已关闭。 - 我在命令提示符中使用了
netstat -an,当服务器启动时,端口 1234 正在监听。客户端连接时,仍然是 LISTENING,表示没有连接。 - 我设置了一些 python 代码通过端口 1234 连接到自身,
但是我在python代码中犯了一个错误,因此服务器
没有关闭,仍然是开放的。所以我决定连接java
客户端到服务器,看看会发生什么。我通过跑步做到了这一点
Client(null)这是非主机的客户端代码。它 导致端口读取 ESTABLISHED,python 服务器是 回响“thump thump”,java代码成功 阅读它。不用挂机,效果很好。
这让我相信问题出在服务器代码上,因为 python 服务器能够与 Java 客户端成功通信,但 Java 客户端无法与 Java 服务器通信。
在执行此测试之前,我一直专注于客户端代码,认为 它 有问题。我在这里发现的所有具有类似症状的问题(请参阅here、here 和here 等)对我来说都是空白,在他们的解决方案中写了(大多数是由于输出流没有刷新,或省略了\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