Jersey 服务器 CLOSE_WAIT 在客户端读取超时后泄漏

Jersey server CLOSE_WAIT leak after read timeout at the client

以下是(大大简化的)代码,用于使用 Jersey 从客户端计算机到 REST 服务器进行通信。开始时建立连接有 5 分钟超时,从底层套接字读取有 2 分钟超时。

public class JerseyClient {

  private final Map<Action, WebResource> resources;

  public JerseyClient(URI uri) {
    this.resources = new EnumMap<Action, WebResource>(Action.class);
    this.resources.put(Action.Put, getClient().resource(Action.Put.getURI()));
    this.resources.put(Action.Query, getClient().resource(Action.Query.getURI()));
  }

  private String submit(Action action, String input) throws Exception {
    WebResource resource = this.resources.get(action);
    ClientResponse response = null;
    synchronized (resource) {
      try {
          response = resource.accept(MediaType.APPLICATION_JSON_TYPE,
                  MediaType.TEXT_PLAIN_TYPE).type(MediaType.APPLICATION_JSON_TYPE).
                  post(ClientResponse.class, input);
          String responseString = null;
          // Handle the response and produce a response string...
        return responseString;
      } finally {
        if (response != null) {
          response.close();
        }
      }
    }
  }

  private static Client getClient() {
    Client client = Client.create();
    client.setReadTimeout(2*60*1000);
    client.setConnectTimeout(5*60*1000);
    return client;
  }

  private enum Action {
    Put, Query;
    public URI getURI(){
      switch (this) {
        case Put:
          return URI.create("PUT_URI");
        case Query:
          return URI.create("QUERY_URI");
        default:
          throw new InvalidStateException("Illegal action");
      }
    }
  }
}

以上代码按预期工作,除非在客户端触发读取超时。在那种情况下,抛出一个 SocketTimeoutException,因此上面 JerseyClient class 的 submit() 方法中的响应对象仍然是 null,所以底层套接字是从未完全关闭。

显然,客户端部分关闭了套接字,因为在另一端,服务器进入 CLOSE_WAIT 状态(即,它已从客户端接收到 FIN 数据包,根据 TCP 规范)。但是,由于它永远不会从客户端获得最终的 ACK(如果调用 response.close() 则应发送),因此它将连接保持在 CLOSE_WAIT(如 netstat 所示) ),因此每次来自客户端的超时 REST 调用都可能在服务器上创建悬空 CLOSE_WAIT

有没有办法解决这个问题,而不需要完全重新设计上面的代码?

你的描述没有意义。状态的名称是 CLOSE_WAIT,而不是 CLOSED_WAIT,它的(正确的)名称表示:它正在等待 local 应用程序关闭套接字,在从对等方接收到远程关闭后。

如果您的服务器正在进入 CLOSE_WAIT:

  1. 客户端已经关闭了套接字。
  2. 服务器没有关闭套接字。这是服务器中的错误。
  3. 服务器永远不会从客户端获得最终的 ACK,直到它通过关闭套接字发出 FIN。 ACK 是对 FIN 的确认。在服务器发出 FIN 之前,客户端无法确认。
  4. 它是从 CLOSE_WAIT 中获取它的关闭,而不是客户端 ACK。
  5. 在客户端调用response.close()与客户端发送最终ACK无关。