我正在研究Java UDP应用程序。应用程序中有一个线程,其唯一的工作是侦听特定端口上的服务器。
我在错误的假设下写了这个应用程序,我正在听的服务器总是在运行;然而,这是一个不好的假设。
如果我的应用程序在服务器运行后启动,那么一切正常。如果我的应用程序在服务器启动之前启动,或者在我的应用程序运行时重新启动服务器,则我的应用程序会中断。
MainClass.java
public class MainClass {
public static void main(String[] args){
ListeningClass myListeningClass = new ListeningClass();
Thread listenerThread = new Thread(myListeningClass);
listenerThread.setName("My Listening Thread");
listenerThread.start();
}
}
ListeningClass.java
public class ListeningClass implements Runnable {
private volatile boolean run = true;
private byte[] receiveBuffer;
private int receiveBufferSize;
private DatagramSocket myDatagramSocket;
private DatagramPacket myDatagramPacket;
@Override
public void run(){
try {
myDatagramSocket = new DatagramSocket(null);
InetSocketAddress myInetSocketAddress = new InetSocketAddress(15347);
myDatagramSocket.bind(myInetSocketAddress);
receiveBuffer = new byte[2047];
myDatagramPacket = new DatagramPacket(receiveBuffer, 2047);
while(run){
myDatagramSocket.receive(myDatagramPacket);
byte[] data = myDatagramPacket.getData();
receiveBufferSize = myDatagramPacket.getLength();
// process the data received here
}
} catch (SocketException se){
// do stuff
} catch (IOException ioe){
// do stuff
}
}
public boolean isRun(){
return run;
}
public void setRun(boolean run){
this.run = run;
}
}
就像我说的,如果我的应用程序在服务器运行后启动,一切都运行正常,就像预期的那样。但是,如果我的应用程序在服务器运行之前启动,则无效。显然,是因为线程尝试打开连接一次,如果失败,那么它永远不会再次尝试。
我将DatagramSocket开放代码移动到while块中,但这并不漂亮。我收到了一堆“已绑定端口”的错误。
那么,我该如何重建它以使其正常工作?
答案 0 :(得分:1)
这不是真正的并发问题。您只需要检查receive
上抛出的异常并进行适当处理。在这种情况下,重新绑定套接字。查看receive
的{{3}}。
例如:
...
while(run) {
try {
myDatagramSocket.receive(myDatagramPacket);
byte[] data = myDatagramPacket.getData();
receiveBufferSize = myDatagramPacket.getLength();
// process the data received here
} catch (IOException ioe) {
// Perhaps use PortUnreachableException but not guaranteed
rebind(myDatagramSocket, myInetSocketAddress);
}
}
private void rebind(DatagramSocket s, InetSocketAddress addr) {
s.bind(addr);
}
我认为这应该足够了。关键是,如果您的receive
表明服务器存在I / O问题,您只想重新绑定。如果你将绑定放在循环中,你就可以为每个接收绑定 - 这在你的快乐路径情况下是正常的。
答案 1 :(得分:1)
这里要注意的重要事项是程序失败的精确点以及您给出的异常类型。
据推测,它在第myDatagramSocket.receive(myDatagramPacket);
行上失败了,但是在你的异常上用stacktrace仔细检查。要检查的第二件事是异常的类型。它是SocketException还是SocketException的子类?是否有特定的错误代码?尝试尽可能具体。
此时,您应该围绕在 while循环中自己的try catch 中失败的代码部分。你希望能够说如果它失败了,你的线程将会休眠并在中断后重试(不要用请求轰炸服务器)。而对于更简单的事情,我会用自己的方法对代码进行分区,所以你会期望像:
public class ListeningClass implements Runnable {
private static final int MAX_RETRIES = 30;
private static final int RETRY_SLEEPTIME = 30000;
private volatile boolean run = true;
private InetSocketAddress myInetSocketAddress;
@Override
public void run(){
try {
DatagramSocket myDatagramSocket = new DatagramSocket(null);
myInetSocketAddress = new InetSocketAddress(15347);
myDatagramSocket.bind(myInetSocketAddress);
byte[] receiveBuffer = new byte[2047];
DatagramPacket myDatagramPacket = new DatagramPacket(receiveBuffer, 2047);
awaitRequests(myDatagramSocket, myDatagramPacket)
} catch (SocketException se){
// do stuff
} catch (IOException ioe){
// do stuff
}
}
private void awaitRequests(DatagramSocket myDatagramSocket, DatagramPacket myDatagramPacket) throws SocketException, IOException {
int maxRetries = MAX_RETRIES;
while(run){
try {
myDatagramSocket.receive(myDatagramPacket);
byte[] data = myDatagramPacket.getData();
// Packet received correctly, reset retry attempts
maxRetries = MAX_RETRIES;
process(myDatagramPacket);
} catch (SocketException e) {
maxRetries--;
// Good place to write to log of some kind
if(maxRetries == 0) {
throw e;
}
Thread.currentThread().sleep(RETRY_SLEEPTIME);
// Lets attempt to restablish the connection
reconnect(myDatagramSocket);
}
}
}
private void process(DatagramPacket myDatagramPacket) {
int receiveBufferSize = myDatagramPacket.getLength();
// process the data received here
}
private void reconnect(DatagramSocket myDatagramSocket) {
myDatagramSocket.bind(myInetSocketAddress);
}
public boolean isRun(){
return run;
}
public void setRun(boolean run){
this.run = run;
}
}
注意几件事。我只捕获了SocketException,因为我假设你得到的异常类型是SocketException。如果你得到某种IOException,那么你应该检查一下。如果您指定该异常的子类型,那就更好了。原因是:您不希望一揽子处理所有错误,而只是处理与服务器关闭的错误。如果程序缺少打开套接字的身份验证,您可能希望立即失败,而不是不断重试。
第二件事是我已经用自己的方法分离了数据包的处理,因为我认为在这些情况下这是正确的做法。