如何中断ServerSocket accept()方法?


143

在主线程中,有一个while(listening)循环,该循环调用accept()ServerSocket对象,然后启动新的客户端线程,并在接受新客户端时将其添加到Collection中。

我也有一个Admin线程,我想使用它发出诸如“ exit”之类的命令,这将导致所有客户端线程被关闭,关闭自身并通过侦听false关闭主线程。

但是,循环中的accept()调用会while(listening)阻塞,并且似乎没有任何方法可以中断它,因此,无法再次检查while条件,并且程序无法退出!

有一个更好的方法吗?还是以某种方式来中断阻塞方法?



对于想要等待x次然后做出反应的人,请使用setSoTimeout()。
阿卜杜拉·奥拉比

Answers:


151

您可以close()从另一个线程进行accept()调用,该调用将引发SocketException


2
谢谢,如此明显,我什至没有想到!退出循环后,我正在调用close()。
lukeo05 2010年

4
奇怪的是,文档中没有此信息:download.oracle.com/javase/6/docs/api/java/net/…方法未标记为引发SocketException。这是只有在这里提到download.oracle.com/javase/1.4.2/docs/api/java/net/...
弗拉季Rastrusny

1
可以调用close(),我的意思是它会在执行此操作时引发异常,所以还有其他方法(不会引发异常,也不基于超时)停止监听请求吗?
库沙尔(Kushal)2012年

3
如果连接已经被接受并且线程正在等待一些数据,该怎么办:while((s = in.readLine())!= null)?
亚历克斯·弗杜洛夫

1
@AlexFedulov关闭该套接字以进行输入。readLine()然后将返回null,并且将发生该线程本应具有的常规关闭操作。
洛恩侯爵,

31

将timeout设置为on accept(),则呼叫将在指定时间后超时阻塞:

http://docs.oracle.com/javase/7/docs/api/java/net/SocketOptions.html#SO_TIMEOUT

设置阻止Socket操作超时:

ServerSocket.accept();
SocketInputStream.read();
DatagramSocket.receive();

必须先设置此选项,然后才能进入阻止操作。如果超时到期并且操作将继续阻塞,java.io.InterruptedIOException则会引发。该Socket不会在这种情况下关闭。



4

您可以为中断服务器创建“ void”套接字。ocket.accept()

服务器端

private static final byte END_WAITING = 66;
private static final byte CONNECT_REQUEST = 1;

while (true) {
      Socket clientSock = serverSocket.accept();
      int code = clientSock.getInputStream().read();
      if (code == END_WAITING
           /*&& clientSock.getInetAddress().getHostAddress().equals(myIp)*/) {
             // End waiting clients code detected
             break;
       } else if (code == CONNECT_REQUEST) { // other action
           // ...
       }
  }

中断服务器周期的方法

void acceptClients() {
     try {
          Socket s = new Socket(myIp, PORT);
          s.getOutputStream().write(END_WAITING);
          s.getOutputStream().flush();
          s.close();
     } catch (IOException e) {
     }
}

4

ServerSocket.close()引发异常的原因 是因为您有一个outputstreaminputstream 该套接字或附加。通过首先关闭输入和输出流,可以安全地避免此异常。然后尝试关闭ServerSocket。这是一个例子:

void closeServer() throws IOException {
  try {
    if (outputstream != null)
      outputstream.close();
    if (inputstream != null)
      inputstream.close();
  } catch (IOException e1) {
    e1.printStackTrace();
  }
  if (!serversock.isClosed())
    serversock.close();
  }
}

您可以调用此方法从任何地方关闭任何套接字而不会出现异常。


7
输入和输出流未附加到,ServerSocket而是附加到,而Socket我们正在谈论关闭而ServerSocket不是Socket,因此ServerSocket可以在不关闭Socket的流的情况下关闭。
icza 2014年


1

好的,我的工作方式可以更直接地解决OP的问题。

请继续阅读简短的答案,以获取有关我如何使用此线程的示例。

简短答案:

ServerSocket myServer;
Socket clientSocket;

  try {    
      myServer = new ServerSocket(port)
      myServer.setSoTimeout(2000); 
      //YOU MUST DO THIS ANYTIME TO ASSIGN new ServerSocket() to myServer‼!
      clientSocket = myServer.accept();
      //In this case, after 2 seconds the below interruption will be thrown
  }

  catch (java.io.InterruptedIOException e) {
      /*  This is where you handle the timeout. THIS WILL NOT stop
      the running of your code unless you issue a break; so you
      can do whatever you need to do here to handle whatever you
      want to happen when the timeout occurs.
      */
}

现实世界的例子:

在此示例中,我有一个ServerSocket等待线程内的连接。关闭应用程序时,我想以干净的方式关闭线程(更具体地说是套接字),然后再关闭应用程序,因此我在ServerSocket上使用.setSoTimeout(),然后使用抛出的中断超时后检查并查看父级是否正在尝试关闭线程。如果是这样,那么我设置关闭套接字,然后设置一个指示线程已完成的标志,然后退出Threads循环并返回null。

package MyServer;

import javafx.concurrent.Task;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import javafx.concurrent.Task;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

public class Server {

public Server (int port) {this.port = port;}

private boolean      threadDone        = false;
private boolean      threadInterrupted = false;
private boolean      threadRunning     = false;
private ServerSocket myServer          = null;
private Socket       clientSocket      = null;
private Thread       serverThread      = null;;
private int          port;
private static final int SO_TIMEOUT    = 5000; //5 seconds

public void startServer() {
    if (!threadRunning) {
        serverThread = new Thread(thisServerTask);
        serverThread.setDaemon(true);
        serverThread.start();
    }
}

public void stopServer() {
    if (threadRunning) {
        threadInterrupted = true;
        while (!threadDone) {
            //We are just waiting for the timeout to exception happen
        }
        if (threadDone) {threadRunning = false;}
    }
}

public boolean isRunning() {return threadRunning;}


private Task<Void> thisServerTask = new Task <Void>() {
    @Override public Void call() throws InterruptedException {

        threadRunning = true;
        try {
            myServer = new ServerSocket(port);
            myServer.setSoTimeout(SO_TIMEOUT);
            clientSocket = new Socket();
        } catch (IOException e) {
            e.printStackTrace();
        }
        while(true) {
            try {
                clientSocket = myServer.accept();
            }
            catch (java.io.InterruptedIOException e) {
                if (threadInterrupted) {
                    try { clientSocket.close(); } //This is the clean exit I'm after.
                    catch (IOException e1) { e1.printStackTrace(); }
                    threadDone = true;
                    break;
                }
            } catch (SocketException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
};

}

然后,在我的Controller类中……(我只会显示相关代码,根据需要将其处理为您自己的代码)

public class Controller {

    Server server = null;
    private static final int port = 10000;

    private void stopTheServer() {
        server.stopServer();
        while (server.isRunning() {
        //We just wait for the server service to stop.
        }
    }

    @FXML private void initialize() {
        Platform.runLater(()-> {
            server = new Server(port);
            server.startServer();
            Stage stage = (Stage) serverStatusLabel.getScene().getWindow();
            stage.setOnCloseRequest(event->stopTheServer());
        });
    }

}

我希望这可以帮助某人。


0

您可以尝试的另一种更清洁的方法是,检查accept循环中的标志,然后在您的管理线程想要取消对accept的线程阻塞时,设置该标志(使其成为线程安全),然后创建一个客户端套接字连接到侦听套接字。accept将停止阻塞并返回新的套接字。您可以计算出一些简单的协议,告诉侦听线程干净地退出线程。然后关闭客户端上的套接字。没有例外,更干净。


这没有任何意义……当您有如下代码时:socket = serverSocket.accept(); 在那一刻,阻塞开始并且循环不是我们代码的一部分,因此有一种方法可以让阻塞代码查找我设置的标志...至少我找不到找到这种方法的方法。 ..如果您有工作代码,请分享?
Michael Sims

是的,似乎我遗漏了一些至关重要的信息,这使它变得毫无意义。您需要使accept套接字成为非阻塞状态,并且仅阻塞一段时间,然后重复循环,然后在该区域中检查退出标志。
斯图

确切地说,如何使接受套接字成为非阻塞的?
Michael Sims's

长期= 1L; if(ioctl(socket,(int)FIONBIO,(char *)&on))
stu

@stu这个问题是针对Java的套接字的。
克鲁夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.