当存在打开的 ASP.NET 4.5 Websocket 时,IIS 应用程序池无法回收



我遇到了一个问题,可以通过以下方式复制(您需要IIS8,因此必须在Windows 8+或Windows Server 2012 R2+上(:

在 IIS 管理器中创建一个新网站,例如端口 8881 上的 TestWs,指向一个新文件夹,例如 C:\temp\testws,然后在其中添加以下 Web.config 文件

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation targetFramework="4.5"/>
    <httpRuntime targetFramework="4.5"/>
  </system.web>
</configuration>

现在在同一文件夹中添加以下 WsHandler.ashx 文件

<%@ WebHandler Language="C#" Class="WsHandler" %>
using System;
using System.Threading;
using System.Web;
public class WsHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.AcceptWebSocketRequest(async webSocketContext =>
        {
            while (true)
            {
                await webSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(new byte[1024]), CancellationToken.None);
            }
        });
    }
    public bool IsReusable { get { return true; } }
}

然后从浏览器的开发人员工具栏中创建一个 websocket,如下所示

var ws = new WebSocket("ws://localhost:8881/wshandler.ashx");
ws.onclose = function() { console.log('closed'); };

在任务管理器中,您将看到此应用程序有一个w3wp.exe进程,如果您将其终止,客户端将引发onclose事件并打印关闭的文本。

但是,如果如上所述创建 websocket 并转到 IIS 管理器并回收应用程序池,则 websocket 将不会关闭,现在将有两个 w3wp.exe 进程。

关闭 Web 套接字ws.close();或刷新浏览器将导致原始 w3wp.exe 进程关闭。

似乎打开的 websocket 的存在导致 IIS 无法正确回收应用程序池。

任何人都可以弄清楚在我的代码中要更改什么或在 IIS 中要更改什么才能使其正常工作吗?

据我所知,当 WebSocket 打开时,IIS 不会拆除应用程序域,因此您会看到这种行为。

我能建议的最好的是你做一些跨进程信号来强制旧实例关闭。您可以使用 EventWaitHandle 实现此目的:

  1. 在 Web 应用程序中创建一个命名的 EventWaitHandle,并在启动时发出信号。

  2. 在单独的线程上,等待等待句柄

  3. 当它发出信号时,调用 HostingEnvironment.InitiateShutdown 以强制任何正在运行的旧实例关闭。

尝试将"关机时间限制"设置为 1 秒(应用程序池>高级设置>进程模型([PS:我没有 IIS8。我正在检查 IIS7 中的属性。

此属性定义分配给工作进程完成处理请求和关闭的时间。如果工作进程超过关闭时间限制,则会终止。

我可以看到 IIS7 中的默认值为 90 秒。如果这在 IIS8 中也是相同的值,那么它可能会为早期的工作进程提供大量时间来完成其工作。90 秒(1.5 分钟(后,它将终止该过程,并且您的 Web 套接字将关闭。如果将其更改为 1 秒,它将终止较早的工作进程几乎立即终止(一旦您回收应用程序池(,您将获得预期的行为。

由于我遇到了同样的问题,这是我想出的解决方案:

在你的IHttpHandler中,你应该有一个继承IStopListeningRegisterObject的静态对象。然后使用 HostingEnvironment.RegisterObject(this( 在回收应用程序池时获得通知(通过 StopListening(。

您还需要一个 CancelTokenSource(也是静态的(,您将在 ReceiveAsync 中移交它。在 StopListening(( 中,您可以使用 CancelTokenSource 的 Cancel(( 来结束等待。然后捕获 OperationCanceledException 并在套接字上调用 Abort((。

不要忘记 Cancel(( 之后的 Dispose((,否则 App-Pool 仍将等待。

相关内容

  • 没有找到相关文章

最新更新