HAPI – 如何正确停止SimpleServer并阻止进一步的连接

huangapple go评论72阅读模式
英文:

HAPI - How to stop SimpleServer correctly and prevent further connections

问题

我正在构建一个应用程序,其中包含由CommunicationProcess类管理的多个服务器和客户端HL7连接。应用程序功能的一部分是在添加新连接时重新启动该进程。客户端连接不会产生问题,因为一旦客户端停止,服务器端无法重新连接。然而,对于服务器连接,我似乎从(相当激进的)客户端那里获得了立即重新连接。以下是我用于停止服务器连接的代码:

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();
    }
}

正如您所看到的,思路是在从CommunicationProcess类接收到停止信号时将全局的AtomicBoolean设置为false,拒绝任何新连接,并停止服务器。然而,在此过程中,客户端仍然保持连接。客户端端是一个我不能透露名称的应用程序,但它已经存在了十多年,我可以确信它不会是问题,因为多年来我一直在支持它作为我的日常工作的一部分,它的行为绝对不会是这样的。

您知道为什么我的代码实际上没有断开连接吗?我觉得我已经对这个API进行了很多探索,但我没有找到取消注册连接侦听器的方法,这可能会修复这个问题。另外,我看不到任何扩展这些服务器类的方法,因为所有东西都被封装和私有化得非常严格。

谢谢。

英文:

I am building an application with several server and client HL7 connections managed by a CommunicationProcess class. Part of the application's functionality is to restart that process when new connections are added. Client connections do not pose a problem because, once the client side stops, there is nothing the server side can do to reconnect. For server connections however, I seem to be getting immediate reconnections from the (rather agressive) client side. This is the code I have to stop a server connection :

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

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

This is my implementation of connectionReceived :

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

As you can see, the idea is to set a global AtomicBoolean to false when receiving the stop signal from the CommunicationProcess class, which denies any new connections, and stop the server. This, somehow, still allows the client to remain connected during this process. The client side is an application I'm not allowed to name but that has existed for well over a decade and I know for a fact it is not gonna be the issue because I've been supporting it as part of my day job for years and it simply does not behave like that.

Any idea why my code doesn't actually kill the connection? I feel like I've explored a lot of this API and I'm not finding a way to UNREGISTER a connection listener which would probably fix this. Also, there is no way that I can see to extend these server classes as everything is rather ferociously encapsulated and privatized.

Thanks

答案1

得分: 1

我正在审查 HAPI 库的代码。

你描述的行为原因可能如下:

当服务器启动时,它会创建一个名为 AcceptorThread 的组件。正如其名称所示,该线程的责任是初始化用于接收传入客户端连接并接受它们的 ServerSocket

与 API 提供的每个 Service 抽象一样,这个线程会在类似以下的循环中运行:

/**
  * 运行线程。
  * 
  * @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 时,它也会尝试停止这个线程。

停止过程基本上会改变控制组件是否 isRunning() 的布尔标志。

正如你所看到的,尽管它将标志设置为 false,但循环中方法 handle 的调用仍必须结束。

这是 AcceptorThreadhandle 方法的实现:

@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

实际上,这个过程就是 AcceptorTreadafterTermination 方法实现:

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

不幸的是 - 你是对的,API 很接近! - 没有明确的方法可以做到这一点。

一种可能的解决方案是实现自己的 HL7Service,命名为 MySimpleServer,使用 SimpleServer 的代码作为基线,只需更改方法 afterTermination 的实现:

/**
  * 关闭套接字
  */
@Override
protected void afterTermination() {
  super.afterTermination();
  // 终止服务器端套接字
  acceptor.afterTermination();
  // 终止接收线程本身
  acceptor.close();
}

请注意:与其调用 acceptor.stop(),我们调用 acceptor.afterTermination() 来直接关闭底层服务器端套接字。

为了避免在 AcceptorThreadhandle 方法中引发的错误,我们还可以从原始方法中实现一个新的类,或者尝试覆盖 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);
}
英文:

I was reviewing the code of the HAPI library.

The cause of the behaviour that you describe could be the following.

When the server starts, they creates a component named AcceptorThread. As it name implies, the responsability of this thread is initialize the ServerSocket that will be used to receive incoming client connections, and accept them.

This thread, as every Service abstraction proposed by the API, runs in a loop like this:

/**
  * 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();
  }

}

When you invoke the method stopAndWait in the server, it will try to stop this thread also.

The stop process basically changes the boolean flag that controls whether the component ``ìsRunning()``` or not.

As you can see, although it sets the flag to false, the invocation of the method handle in the loop still must end.

This is the implementation of the AcceptorThread handle method:

@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);
  }
}

As you can see, the method invokes ServerSocket.accept, thus allowing new incoming connections.

In order to disconnect this server side socket, we can call close from another thread.

In fact, this process is the one implemented by the AcceptorTread afterTermination method:

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

Unfortunally - you are right, the API is very close! - there is no a clear way to do that.

One possible solution could be implement your own HL7Service, name it, MySimpleServer, using the code of SimpleServer as a baseline, and just changing the implementation of the method afterTermination:

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

Please, pay attention: instead of call acceptor.stop() we invoke acceptor.afterTermination() to close directly the underlying server side socket.

To avoid the errors raised by the handle method in AcceptorThread, we can also implement a new class from the original one, or just trying to overwrite the handle method to take into account if the server side socket is closed:

@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);
  }
}

For testing, you can try something like this:

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);
}

huangapple
  • 本文由 发表于 2020年5月5日 21:05:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/61613882.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定