You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

重复在临时端口启动服务器时的连接处理问题求助

在临时端口启动服务器时的连接处理问题求助

大家好,我现在遇到一个挺棘手的问题,想请各位帮忙分析下:

问题场景

我写了一段循环逻辑:每次迭代里构造并启动一个Jetty服务器,然后创建HTTP客户端发送请求,打印响应码后再清理掉服务器和客户端。但运行几千次迭代后,偶尔会出现连接超时或者其他IO错误,而且这个问题不仅在WireMock测试里出现,用纯Jetty也能复现,MacOS 15.6.1和Linux CI环境下都有这个情况。

已尝试的排查与优化措施

我已经针对常见的可能原因做了排查,但问题依然存在:

  • netstat -np tcp检查过临时端口的使用情况,确认是否耗尽
  • 给Jetty连接器设置了connector.setReuseAddress(true);,想开启SO_REUSEADDR来复用端口
  • 在MacOS上通过sudo sysctl -w net.inet.tcp.msl=1把TCP最大分段生存期改成1,减少TIME_WAIT状态的端口占用
  • 怀疑是GC频繁导致的问题,特意增大了JVM堆内存,但现象没有改善

错误示例

连接超时错误

Exception in thread "main" java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:949)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
    at com.example.Main.main(Main.java:43)
Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68)
    at java.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1788)
    at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:1386)
Caused by: java.net.ConnectException: HTTP connect timed out
    at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69)
    ... 2 more

EOF读取错误

Exception in thread "main" java.io.IOException: HTTP/1.1 header parser received no bytes
    at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:970)
    at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:133)
    at com.example.Main.main(Main.java:43)
Caused by: java.io.IOException: HTTP/1.1 header parser received no bytes
    at java.net.http/jdk.internal.net.http.common.Utils.wrapWithExtraDetail(Utils.java:412)
    at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.onReadError(Http1Response.java:590)
    at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.checkForErrors(Http1AsyncReceiver.java:302)
    at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:268)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:182)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:207)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.io.EOFException: EOF reached while reading
    at java.net.http/jdk.internal.net.http.Http1AsyncReceiver$Http1TubeSubscriber.onComplete(Http1AsyncReceiver.java:601)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadSubscription.signalCompletion(SocketTube.java:648)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:853)
    at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:181)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:207)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:280)
    at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:233)
    at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:782)

复现代码(服务器部分)

下面是我用的服务器实现类(补全了部分截断的代码):

package com.example;

import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Callback;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class SingleEndpointServer implements AutoCloseable {
    private final Server server;

    public SingleEndpointServer() {
        this.server = new Server();
        ServerConnector connector = new ServerConnector(server);
        connector.setReuseAddress(true); // 尝试开启SO_REUSEADDR
        server.addConnector(connector);
        
        // 设置简单的请求处理器
        server.setHandler(new Handler() {
            @Override
            public boolean handle(Request request, Response response, Callback callback) throws Exception {
                response.setStatus(200);
                response.getHeaders().add(HttpHeader.CONTENT_TYPE, "text/plain");
                String body = "OK";
                ByteBuffer buffer = StandardCharsets.UTF_8.encode(body);
                response.write(true, buffer, callback);
                return true;
            }
        });
    }

    public void start() throws Exception {
        server.start();
        // 获取实际绑定的临时端口
        int port = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
        System.out.println("Server started on port " + port);
    }

    @Override
    public void close() throws Exception {
        server.stop();
        server.destroy();
    }
}

目前我已经排查了端口复用、TIME_WAIT状态、GC等常见方向,但问题依然存在,想请教各位还有哪些可能的原因?或者有没有进一步排查的思路?谢谢大家!

火山引擎 最新活动