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同时等待新连接事件和关停事件,任意事件触发就往下执行。 - 收到关停信号后按顺序执行清理:
- 第一时间调用
closesocket关闭监听Socket,阻止新连接接入 - 遍历所有活跃连接,停止读取新请求,将已接收请求的待返回响应全部发送完成
- 等待活跃连接全部断开,设置等待上限比配置的
shutdownTimeLimit少5秒,留足资源释放余量 - 依次关闭所有连接Socket、释放日志/文件/堆内存等全局资源,调用
WSACleanup() - 从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
相关产品推荐
相关产品推荐

