HAPI - Как правильно остановить SimpleServer и предотвратить дальнейшие подключения - PullRequest
0 голосов
/ 05 мая 2020

Я создаю приложение с несколькими серверными и клиентскими соединениями HL7, управляемыми классом CommunicationProcess. Частью функциональных возможностей приложения является перезапуск этого процесса при добавлении новых подключений. Клиентские подключения не представляют проблемы, потому что после остановки клиентской стороны сервер уже ничего не может сделать для повторного подключения. Однако для серверных подключений я, кажется, получаю немедленные переподключения с (довольно агрессивной) клиентской стороны. Это код, который мне нужно для остановки соединения с сервером:

public void disconnect() 
{
    usageServer.getRemoteConnections().forEach((connection) -> connection.close());
    usageServer.stopAndWait();
    usageServer.getRemoteConnections().forEach((connection) -> connection.close());   
}

public void stop()
{
    running.set(false);

    disconnect();
}

Это моя реализация connectionReceived:

@Override
public void connectionReceived(Connection theC) 
{
    if (running.get())
    {
        setStatus(ConnectionStatus.CONNECTED);
    }
    else
    {
        theC.close();
    }
}

Как видите, идея состоит в том, чтобы установить глобальный AtomicBoolean значение false при получении сигнала остановки от класса CommunicationProcess, который запрещает любые новые соединения, и останавливает сервер. Это каким-то образом все еще позволяет клиенту оставаться на связи во время этого процесса. Клиентская сторона - это приложение, которое мне не разрешено называть, но оно существует уже более десяти лет, и я точно знаю, что это не будет проблемой, потому что я поддерживал его как часть моей повседневной работы в течение многих лет. и он просто так себя не ведет.

Есть идеи, почему мой код на самом деле не убивает соединение? Я чувствую, что изучил много этого API, и я не нахожу способа ОТМЕНА РЕГИСТРАЦИИ прослушивателя соединения, который, вероятно, исправил бы это. Кроме того, я не вижу возможности расширить эти серверные классы, поскольку все довольно жестко инкапсулировано и приватизировано.

Спасибо

1 Ответ

1 голос
/ 19 августа 2020

Я просматривал код библиотеки HAPI.

Причина описываемого вами поведения могла быть следующей.

Когда сервер запускается, они создают компонент с именем AcceptorThread. Как следует из названия, ответственность этого потока заключается в инициализации ServerSocket, который будет использоваться для приема входящих клиентских подключений, и их приема.

Этот поток, как и каждая Service абстракция, предложенная API, работает в al oop следующим образом:

/**
  * Runs the thread.
  * 
  * @see java.lang.Runnable#run()
  */
public final void run() {
  try {
    afterStartup();
    log.debug("Thread {} entering main loop", name);
    while (isRunning()) {
      handle();
      startupLatch.countDown();
    }
    log.debug("Thread {} leaving main loop", name);
  } catch (RuntimeException t) {
    if (t.getCause() != null) {
      serviceExitedWithException = t.getCause();
    } else {
      serviceExitedWithException = t;
    }
    log.warn("Thread exiting main loop due to exception:", t);
  } catch (Throwable t) {
    serviceExitedWithException = t;
    log.warn("Thread exiting main loop due to exception:", t);
  } finally {
    startupLatch.countDown();
    afterTermination();
  }

}

Когда вы вызываете метод stopAndWait на сервере, он также попытается остановить этот поток.

Процесс остановки в основном меняется флаг boolean, который контролирует, будет ли компонент `` ìsRunning () '' или нет.

Как видите, хотя он устанавливает флаг в false, вызов метода handle в l oop все еще должен заканчиваться.

Это реализация метода AcceptorThread handle:

@Override
protected void handle() {
  try {
    Socket s = ss.accept();
    socketFactory.configureNewAcceptedSocket(s);
    if (!queue.offer(new AcceptedSocket(s))) {
      log.error("Denied enqueuing server-side socket {}", s);
      s.close();
    } else
      log.debug("Enqueued server-side socket {}", s);
  } catch (SocketTimeoutException e) { /* OK - just timed out */
    log.trace("No connection established while waiting");
  } catch (IOException e) {
    log.error("Error while accepting connections", e);
  }
}

Как видите, метод вызывает ServerSocket.accept , таким образом разрешая новые входящие соединения.

Чтобы отключить этот серверный сокет, мы можем вызвать close из другого потока.

Фактически, этот процесс реализован AcceptorTread afterTermination метод:

@Override
protected void afterTermination() {
  try {
    if (ss != null && !ss.isClosed())
      ss.close();
  } catch (IOException e) {
    log.warn("Error during stopping the thread", e);
  }
}

Unfortu наконец - вы правы, API очень близко! - нет четкого способа сделать это.

Одним из возможных решений может быть реализация вашего собственного HL7Service, назовите его MySimpleServer, используя код SimpleServer в качестве базовой линии и просто изменив реализация метода afterTermination:

/**
  * Close down socket
  */
@Override
protected void afterTermination() {
  super.afterTermination();
  // Terminate server side socket
  acceptor.afterTermination();
  // Terminate the acceptor thread itself
  acceptor.close();
}

Обратите внимание: вместо вызова acceptor.stop() мы вызываем acceptor.afterTermination(), чтобы напрямую закрыть сокет на стороне сервера.

Чтобы избежать ошибок, вызванных методом handle в AcceptorThread, мы также можем реализовать новый класс из исходного или просто попытаться перезаписать метод handle, чтобы учесть, закрыт ли сокет на стороне сервера:

@Override
protected void handle() {
  try {
    if (ss.isClosed()) {
      log.debug("The server-side socket is closed. No new connections will be allowed.");
      return;
    }

    Socket s = ss.accept();
    socketFactory.configureNewAcceptedSocket(s);
    if (!queue.offer(new AcceptedSocket(s))) {
      log.error("Denied enqueuing server-side socket {}", s);
      s.close();
    } else
      log.debug("Enqueued server-side socket {}", s);
  } catch (SocketTimeoutException e) { /* OK - just timed out */
    log.trace("No connection established while waiting");
  } catch (IOException e) {
    log.error("Error while accepting connections", e);
  }
}

Для тестирования можно попробовать примерно так:

public static void main(String[] args) throws Exception {

  HapiContext ctx = new DefaultHapiContext();

  HL7Service server = new MySimpleServer(8888);
  server.startAndWait();

  Connection client1 = ctx.newClient("127.0.0.1", 8888, false);

  server.getRemoteConnections().forEach((connection) -> connection.close());

  server.stopAndWait();

  try {
    Connection client2 = ctx.newClient("127.0.0.1", 8888, false);
  } catch (Throwable t) {
    t.printStackTrace();
  }

  ctx.close();

  System.exit(0);
}
...