You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何借助Apache Commons Daemon实现NanoHTTPD Windows服务跨进程启停?

解决NanoHTTPD服务通过procrun管理及外部进程停止的问题

看起来你需要搞定两个核心问题:一是让你的NanoHTTPD服务能被Apache Commons Daemon的procrun作为Windows服务管理,二是从独立Java进程触发服务停止。下面我一步步给你拆解解决方案:

1. 修改PrintServer类,集成Daemon接口并添加停止逻辑

首先,你的PrintServer需要实现org.apache.commons.daemon.Daemon接口——这是procrun管理服务生命周期的标准入口。同时我们要加线程安全的停止机制,以及一个用于外部触发停止的HTTP接口(用HTTP方式最贴合你的服务场景,当然你也可以选监听特定端口、文件标记等方式)。

修改后的完整代码如下:

import org.apache.commons.daemon.Daemon;
import org.apache.commons.daemon.DaemonContext;
import org.apache.commons.daemon.DaemonInitException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.iki.elonen.NanoHTTPD;
import fi.iki.elonen.NanoHTTPD.IHTTPSession;
import fi.iki.elonen.NanoHTTPD.Response;
import fi.iki.elonen.NanoHTTPD.Response.Status;

public class PrintServer extends NanoHTTPD implements Daemon {
    private static final Logger log = LoggerFactory.getLogger(PrintServer.class);
    private static final int SERVER_PORT = 11100;
    private static final String MIMEJSON = "application/json";
    // 自定义停止请求的验证密钥,防止非法调用
    private static final String SHUTDOWN_KEY = "your_secure_random_key_here";
    // 线程安全的停止标记,避免重复触发停止
    private volatile boolean isShuttingDown = false;

    public PrintServer() throws IOException {
        super(SERVER_PORT);
    }

    @Override
    public void init(DaemonContext context) throws DaemonInitException {
        // 这里可以加初始化逻辑,比如加载配置文件,暂时留空也没问题
        log.info("PrintServer daemon initialized");
    }

    @Override
    public void start() throws Exception {
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
        log.info("Cinebot PrintServer avviato e in ascolto su porta {}", SERVER_PORT);
    }

    @Override
    public void stop() throws Exception {
        shutdownServer();
        log.info("PrintServer stopped via daemon command");
    }

    @Override
    public void destroy() {
        // 清理资源,比如关闭数据库连接、释放文件句柄等
        log.info("PrintServer daemon destroyed");
    }

    private void shutdownServer() {
        if (!isShuttingDown) {
            isShuttingDown = true;
            super.stop();
        }
    }

    @Override
    public Response serve(IHTTPSession session) {
        // 处理停止请求
        if ("/shutdown".equals(session.getUri())) {
            String providedKey = session.getParms().get("key");
            if (SHUTDOWN_KEY.equals(providedKey)) {
                // 异步执行停止逻辑,避免阻塞HTTP响应
                new Thread(this::shutdownServer).start();
                return newFixedLengthResponse(Status.OK, MIMEJSON, "{\"status\":\"shutdown initiated\"}");
            } else {
                return newFixedLengthResponse(Status.UNAUTHORIZED, MIMEJSON, "{\"error\":\"invalid shutdown key\"}");
            }
        }
        // 原有业务逻辑
        return newFixedLengthResponse(Status.OK, MIMEJSON, null);
    }

    public static void main(String[] args) {
        try {
            PrintServer server = new PrintServer();
            server.start();
        } catch (IOException ioe) {
            log.error("Non posso avviare PrintServer", ioe);
        }
    }
}

关键改动说明:

  • 实现Daemon接口:procrun会自动调用init()、start()、stop()、destroy()来管理服务的启停和销毁,替代了你原来在构造方法里直接启动的逻辑。
  • 线程安全的停止标记:用volatile修饰isShuttingDown,确保多线程环境下停止操作的正确性,避免重复停止。
  • 安全的停止接口:新增/shutdown接口,通过密钥验证后才触发停止,防止恶意访问;停止逻辑放在异步线程里,避免阻塞HTTP请求的响应。

2. 配置procrun安装Windows服务

procrun需要明确指定Daemon实现类、类路径、JVM参数等。假设你的项目打包成print-server.jar,所有依赖(commons-daemon.jar、nanohttpd.jar、slf4j相关包)都放在lib目录下,可以用以下命令安装服务:

# 安装名为PrintServer的Windows服务
prunsrv.exe //IS//PrintServer ^
    --DisplayName="Cinebot Print Server" ^
    --Description="HTTP Server for Cinebot Print Service" ^
    --Classpath=print-server.jar;lib\* ^
    --StartMode=jvm ^
    --StartClass=com.yourpackage.PrintServer ^
    --StartMethod=start ^
    --StopMode=jvm ^
    --StopClass=com.yourpackage.PrintServer ^
    --StopMethod=stop ^
    --JvmOptions="-Dfile.encoding=UTF-8" ^
    --LogPath=.\logs ^
    --StdOutput=auto ^
    --StdError=auto

命令要点:

  • //IS//PrintServer:指定要安装的服务名称为PrintServer。
  • --StartClass/--StopClass:填写你的PrintServer类的全限定名(比如com.yourpackage.PrintServer)。
  • --StartMethod=start/--StopMethod=stop:对应Daemon接口的方法,procrun会自动调用这些方法完成启停。
  • --Classpath:要包含你的项目jar和所有依赖jar,用分号分隔。

卸载服务的命令:

prunsrv.exe //DS//PrintServer

3. 从独立Java进程停止服务

你可以写一个简单的Java程序,通过发送HTTP请求到/shutdown接口来触发停止:

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class StopPrintServer {
    public static void main(String[] args) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:11100/shutdown?key=your_secure_random_key_here"))
                .GET()
                .build();

        try {
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println("Shutdown response: " + response.body());
            if (response.statusCode() == 200) {
                System.out.println("PrintServer shutdown initiated successfully");
            } else {
                System.err.println("Failed to shutdown PrintServer: " + response.body());
            }
        } catch (IOException | InterruptedException e) {
            System.err.println("Error sending shutdown request: " + e.getMessage());
        }
    }
}

运行这个程序,只要密钥正确,就能触发PrintServer的停止逻辑,同时procrun会感知到服务停止,完成完整的服务停止流程。

注意事项

  • 务必把SHUTDOWN_KEY设置为安全的随机字符串,避免被恶意调用。
  • 如果你的服务需要等待正在处理的请求完成后再停止,可以在shutdownServer()方法里添加等待逻辑,比如遍历并等待所有工作线程结束。
  • 确保prunsrv.exe的位数(32/64位)和你的Java版本匹配,否则可能出现服务启动失败的问题。

内容的提问来源于stack exchange,提问作者Tobia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:02:13