两个Jersey进程监听同一端口但请求响应范围不同的排查求助
我有两个基于Jersey 2.X的简单应用,都监听TCP HTTP端口4466:一个是通过java UncompiledMain.java运行的JVM进程,另一个是Java原生镜像(native-image)生成的进程(正在从JIT编译迁移到原生镜像)。通过端口查看工具得到如下结果:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME 1) java 71457 sarnobat 73u IPv6 0x5e313f1de8147dd9 0t0 TCP *:4466 (LISTEN) 2) app 71552 sarnobat 5u IPv6 0x5e313f1db096eb79 0t0 TCP localhost:4466 (LISTEN)
两者的运行时绑定方式不同(详情见下文)。我知道新版Linux内核允许多个监听器绑定同一端口,但不确定这和我在Mac OS X 10.15.7上遇到的情况是否相关。想搞懂这一现象,最终实现让单个程序处理所有请求,但目前对相关网络基础知识理解不足,求指点。
详情
抱歉代码比较杂乱——本来打算先跑通再整理!
服务器1(JVM运行)- org.glassfish.jersey.jdkhttp.JdkHttpServerFactory
可响应网络请求(包括SSH隧道过来的请求)。
JdkHttpServerFactory.createHttpServer(new URI("http://localhost:" + args[0] + "/"), new ResourceConfig(MyResource.class)); ... @javax.ws.rs.Path("") public static class MyResource { // 必须为public @GET @javax.ws.rs.Path("") @Produces("application/json") public Response list(@QueryParam("value") String iValue) throws JSONException, IOException { System.err.println("list()"); System.out.println(iValue); return Response.ok().header("Access-Control-Allow-Origin", "*").type("application/json").build(); }
服务器2(原生镜像)- org.glassfish.grizzly.http.server.HttpServer
仅能响应本地请求,无法处理网络请求。
private static final URI BASE_URI = URI.create("http://localhost:4466/"); public static final String ROOT_PATH = ""; public static void main(String[] args) { try { System.out.println("\"Hello World\" Jersey Example App"); final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, create(), true); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { server.shutdownNow(); } })); server.start(); ... public static ResourceConfig create() { final Resource.Builder resourceBuilder = Resource.builder(ROOT_PATH); resourceBuilder.addMethod("GET").handledBy(new Inflector<ContainerRequestContext, Response>() { @Override public Response apply(ContainerRequestContext data) { String iValue = data.getUriInfo().getQueryParameters().getFirst("value"); System.err.println("list()"); System.out.println(iValue); getMethodCalled = true; return Response.ok().header("Access-Control-Allow-Origin", "*").type("application/json").build(); } });
问题解析与解决方法
1. 为何两个进程能同时绑定4466端口?
从端口监听信息能看到关键差异:
- JVM进程绑定的是
*:4466:*代表通配所有网络接口(包括本地回环、物理网卡等所有可用IP) - 原生镜像进程绑定的是
localhost:4466:仅绑定本地回环接口(127.0.0.1或::1)
在Mac OS X(BSD内核衍生)中,这种绑定是允许的:当一个进程绑定通配接口,另一个进程绑定特定接口的同一端口,系统会根据请求的目标IP来分发流量。
2. 为何原生镜像进程无法响应网络请求?
因为它只绑定了本地回环接口,只有来自本机的请求(比如curl http://localhost:4466)会被它处理,而外部网络请求(包括SSH隧道的请求,目标是机器的公网/局域网IP)会被绑定通配接口的JVM进程接收。
3. 实现单程序处理所有请求的方案
方案一:修改原生镜像的绑定地址
把绑定URI从http://localhost:4466/改成http://0.0.0.0:4466/(IPv4通配)或http://[::]:4466/(IPv6通配),这样原生镜像进程会绑定所有网络接口。之后关闭JVM进程,所有请求就会由原生镜像进程处理。
修改后的代码片段:
private static final URI BASE_URI = URI.create("http://0.0.0.0:4466/");
方案二:统一绑定策略
如果要保留其中一个进程,确保它绑定通配接口*(对应代码里用0.0.0.0或省略主机名的方式),同时关闭另一个进程即可。
补充说明
- 若两个进程都绑定通配接口
*:4466,Mac OS X会抛出端口占用错误,无法同时启动。 localhost绑定仅适用于本地调试场景,对外提供服务时必须绑定通配接口或特定的公网/局域网IP。
内容的提问来源于stack exchange,提问作者Sridhar Sarnobat

