Обзор
Отличный вопрос! Чтобы повторить то, что было сказано в вышеприведенных комментариях, вы ищете завершение работы на стороне сервера. Есть некоторый способ справиться с этой ситуацией, и я могу объяснить это на кратком примере.
ExecutorServer
Я рассмотрю модифицированный пример, основанный на этом примере. Ниже найдите реализацию сервера.
class NetworkService implements Runnable {
private final ServerSocket serverSocket;
private final ExecutorService pool;
private final AtomicBoolean shouldExit;
public NetworkService(int port, int poolSize) throws IOException {
serverSocket = new ServerSocket(port);
pool = Executors.newFixedThreadPool(poolSize);
shouldExit = new AtomicBoolean(false); // Thread-safe boolean
}
public void run() { // run the service
try {
// While we should not exit
while(!shouldExit.get()) {
try {
pool.execute(new ClientHandler(serverSocket.accept()));
} catch (SocketException e) {
if(shouldExit.get()) break; // Poison pill has been delivered, lets stop
// Error handling
}
}
} catch (IOException ex) {
pool.shutdown();
}
// Clean up the thread pool
shutdownAndAwaitTermination();
}
}
class ClientHandler implements Runnable {
private final Socket socket;
ClientHandler (Socket socket) { this.socket = socket; }
public void run() {
...
}
...
}
Здесь вы измените свой текущий код Сервера, чтобы запугать эту структуру. У вас сейчас похожий макияж, но мы добавили ExecutorService
.
Исполнитель, который предоставляет методы для управления завершением и методы, которые могут создать будущее для отслеживания хода выполнения одной или нескольких асинхронных задач.
Отправляя свой ClientHandler на ExecutorService
, вы используете ThreadPool
. Хотя это дает множество преимуществ, наиболее значительными из них являются то, что вы имеете больший контроль над многопоточным сервисом, ThreadPool
будет управлять использованием потоков, а эффективность приложения значительно возрастет.
Ниже показано, как вы пытаетесь завершить работу всех завершающих потоков:
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
Теперь остается вопрос, как отключить сервер? Приведенный выше код показывает улучшенную структуру, но все еще имеет проблему блокировки на serverSocket.accept()
!
Решение
Есть две идеи, которые приходят на ум, когда думаешь об этом сценарии; CLI или графический интерфейс. Оба имеют одинаковую семантику, и решение в конечном итоге остается за вами. Для целей объяснения я буду ссылаться на подход CLI.
Таблетка отравления
Если вы реализуете new Thread()
, который обрабатывает все входящие команды из CLI, этот поток будет действовать как ядовитая таблетка . Идея состоит в том, чтобы доставить таблетку с ядом к цели так, чтобы она могла проснуться / выполнить и умереть. Поток изменит логическое значение shouldExit
на true
и создаст new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()).close();
для подключения к ServerSocket
и немедленно закроет его. В приведенном выше коде приложение больше не будет блокировать serverSocket.accept()
. Вместо этого он войдет в пробный улов для SocketExceptions
и проверит, использовалась ли ядовитая таблетка; Если это было так, давайте очистим, если нет - разрешим ошибку.
Таймаут
Вы также можете установить тайм-аут на ServerSocket
так, что он будет выдавать исключение каждый раз, когда не может установить соединение в этом интервале времени с myServer.setSoTimeout(2000);
. Это выдаст InterruptedIOException
и может быть обработано аналогично таблетке с ядом, где флаг изменяется с помощью команды CLI, и он проверяет, должен ли он выйти в блоке catch. Если это должно завершиться, давайте очистим, если нет - обработаем ошибку.