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

IIS重启或App Pool回收时如何实现托管Winsock服务的干净关停

IIS托管Winsock服务(httpPlatformHandler模式)优雅关停实现方案

核心触发逻辑

httpPlatformHandler在IIS站点重启、应用池回收、站点停止场景下,不会直接强制终止你的服务进程,执行逻辑如下:

  • 立刻停止将新入站请求转发到Winsock服务监听端口
  • 向托管的服务进程发送CTRL_SHUTDOWN_EVENT控制台事件
  • 按照web.config中配置的shutdownTimeLimit时长等待进程自行退出,默认等待时长仅10秒
  • 等待超时后才会调用TerminateProcess强制结束进程
    重叠回收场景下,新进程会在旧进程收到关停信号的同时启动,直接接管新请求,不会影响新流量接入,只需要旧进程处理完存量请求自行退出即可。

具体代码与配置修改

1. 注册控制台关停事件回调

httpPlatformHandler传递的关停信号是Windows标准控制台事件,不需要额外引入SDK,直接通过系统API捕获即可。
注意不要在事件回调中执行阻塞操作,该回调运行在系统控制台控制线程中,系统给的执行窗口极短,阻塞会直接导致流程被中断,进程被强杀。
参考实现代码:

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <atomic>

// 全局关停标记,多线程访问用原子变量避免竞争
std::atomic<bool> g_shutdownFlag = false;
// 全局关停事件,用于唤醒阻塞的工作线程
HANDLE g_shutdownEvent = nullptr;

// 控制台事件处理回调
BOOL WINAPI ConsoleCtrlHandler(DWORD ctrlType) {
    // 捕获关停、控制台关闭两类事件
    if (ctrlType == CTRL_SHUTDOWN_EVENT || ctrlType == CTRL_CLOSE_EVENT) {
        g_shutdownFlag.store(true);
        // 触发事件唤醒所有阻塞等待的线程
        SetEvent(g_shutdownEvent);
        return TRUE;
    }
    return FALSE;
}

// 程序初始化阶段调用
bool InitShutdownHandler() {
    // 创建手动重置的关停事件
    g_shutdownEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
    if (!g_shutdownEvent) return false;

    // 注册控制台回调
    if (!SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE)) {
        CloseHandle(g_shutdownEvent);
        return false;
    }
    return true;
}

踩坑提示:如果程序编译时选择了Windows子系统(无默认控制台窗口,即链接选项/SUBSYSTEM:WINDOWS),需要在初始化阶段调用AllocConsole()关联控制台,否则无法收到控制台事件。不需要显示控制台窗口的话,后续调用ShowWindow(GetConsoleWindow(), SW_HIDE)隐藏即可,不影响事件接收。

2. 调整Winsock服务运行逻辑

不要让工作线程无限阻塞在accept、recv等调用上,否则即使收到关停信号也无法及时响应,调整逻辑如下:

  • 监听Socket不要用无限阻塞模式,要么通过setsockopt设置SO_RCVTIMEO为1000ms,每次超时后轮询关停标记;要么通过WSAEventSelect将监听Socket与前面创建的g_shutdownEvent绑定,用WSAWaitForMultipleEvents同时等待新连接事件和关停事件,任意事件触发就往下执行。
  • 收到关停信号后按顺序执行清理:
    1. 第一时间调用closesocket关闭监听Socket,阻止新连接接入
    2. 遍历所有活跃连接,停止读取新请求,将已接收请求的待返回响应全部发送完成
    3. 等待活跃连接全部断开,设置等待上限比配置的shutdownTimeLimit少5秒,留足资源释放余量
    4. 依次关闭所有连接Socket、释放日志/文件/堆内存等全局资源,调用WSACleanup()
    5. 从main函数正常return退出进程,不要调用ExitProcess、TerminateProcess强制退出

3. 对齐httpPlatformHandler与应用池配置

修改站点目录下的web.config,给httpPlatformHandler设置足够的关停等待时长,匹配你的服务最长请求处理时间:

<configuration>
  <system.webServer>
    <handlers>
      <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
    </handlers>
    <httpPlatform
      processPath="C:\your-service-path\your-winsock-app.exe"
      arguments="--port %HTTP_PLATFORM_PORT%"
      port="%HTTP_PLATFORM_PORT%"
      startupTimeLimit="30"
      shutdownTimeLimit="30" <!-- 关停等待时长,单位秒,根据业务最长请求耗时设置 -->
      stdoutLogEnabled="true"
      stdoutLogFile=".\logs\service"
    />
  </system.webServer>
</configuration>

同时检查对应应用池的配置:进入IIS管理器→应用池→高级设置,确认「进程模型」→「关闭时间限制」的值大于等于你在httpPlatform中设置的shutdownTimeLimit,避免应用池提前强杀进程,该值默认是90秒,一般不需要调整。

验证方法

配置完成后按以下步骤验证优雅关停是否生效:

  • 启动站点,发起几个耗时较长的测试请求(比如大文件下载、慢处理接口)
  • 手动在IIS管理器中回收对应应用池/重启站点
  • 检查服务日志,确认存在完整的「收到关停信号→关闭监听→等待存量请求→资源释放→进程退出」链路,无崩溃、强制终止相关错误
  • 观察任务管理器,旧服务进程不会立刻消失,会在存量请求处理完成后自行退出
  • 检查正在进行的测试请求,确认没有出现连接重置、中途断开的问题,能正常返回完整响应

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 14:33:22