SignalR在Linux/Mono上使用Owin自托管时,当客户端连接丢失时会出现Socket异常。

7

我正在运行一个非常简单的SignalR服务器,通过Owin在Ubuntu Server 14.04上进行自托管,使用的是Mono 3.2.8。(下方为代码)

连接/断开连接在远程Windows服务器以及将程序部署到Linux服务器时都能正常工作。但是当客户端意外死机而没有通知SignalR他已经断开连接时,在Linux服务器上会出现永无止境的SocketException异常,而在Windows服务器上则是大约30秒后就会断开客户端连接。但是在Linux服务器上,这个socketexception (如下)每隔10秒左右就会不停地报错。

我该如何让Linux服务器在运行相同的代码时像Windows服务器一样,设定一段超时时间后断开用户连接,并且不抛出socketexceptions?

服务器代码:

using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Owin;

namespace signalrtestserver
{
    class Program
    {
        static void Main(string[] args)
        {
            var uri = System.Configuration.ConfigurationManager.AppSettings["startup_uri"] ?? "http://*:7890";
            using (Microsoft.Owin.Hosting.WebApp.Start<Startup>(uri))
            {
                Console.WriteLine(string.Format("Server started on {0}. Press enter to close.", uri));
                Console.ReadLine();
            }
        }
    }

    class Startup
    {
        static Hub hub;

        public void Configuration(IAppBuilder app)
        {
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
            var configuration = new HubConfiguration();
            configuration.EnableDetailedErrors = true;
            app.MapSignalR("/signalr", configuration);
            hub = new MyHub();
        }
    }

    public class MyHub : Hub
    {
        public override Task OnConnected() { Console.WriteLine(Context.ConnectionId + " connected"); return base.OnConnected(); }
        public override Task OnDisconnected() { Console.WriteLine(Context.ConnectionId + " disconnected"); return base.OnDisconnected(); }
        public override Task OnReconnected() { Console.WriteLine(Context.ConnectionId + " reconnected"); return base.OnReconnected(); }
    }
}

客户端代码:

using System;
using System.Net;
using Microsoft.AspNet.SignalR.Client;

namespace signalrconnection
{
    class Program
    {
        static void Main(string[] args)
        {
            var uri = System.Configuration.ConfigurationManager.AppSettings["signalr_uri"] ?? "http://localhost:7890/signalr";
            ServicePointManager.DefaultConnectionLimit = 10;
            var hubConnection = new HubConnection(uri, false);
            hubConnection.StateChanged += stateChange => Console.WriteLine(string.Format("SignalR {0} >> {1} ({2})", stateChange.OldState, stateChange.NewState, hubConnection.Transport == null ? "<<null>>" : hubConnection.Transport.Name));
            var hubProxy = hubConnection.CreateHubProxy("MyHub");
            hubConnection.Start();
            Console.WriteLine("Press enter to die...");
            Console.ReadLine();
            //hubConnection.Dispose(); //uncomment this to simulate a graceful disconnect which works on both windows and linux
        }
    }
}

无尽的 Mono 异常:

{path-to-project}/Microsoft.AspNet.SignalR.Core.dll Error : 0 : SignalR exception thrown by Task: System.AggregateException: One or more errors occured ---> System.IO.IOException: Write failure ---> System.Net.Sockets.SocketException: Connection reset by peer
  at System.Net.Sockets.Socket.Send (System.Byte[] buf, Int32 offset, Int32 size, SocketFlags flags) [0x00000] in <filename unknown>:0
  at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in <filename unknown>:0
  at System.Net.ResponseStream.InternalWrite (System.Byte[] buffer, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at System.Net.ResponseStream.Write (System.Byte[] buffer, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at Microsoft.Owin.Host.HttpListener.RequestProcessing.ExceptionFilterStream.Write (System.Byte[] buffer, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
 --> (Inner exception 0) System.IO.IOException: Write failure ---> System.Net.Sockets.SocketException: Connection reset by peer
  at System.Net.Sockets.Socket.Send (System.Byte[] buf, Int32 offset, Int32 size, SocketFlags flags) [0x00000] in <filename unknown>:0
  at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in <filename unknown>:0
  at System.Net.ResponseStream.InternalWrite (System.Byte[] buffer, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at System.Net.ResponseStream.Write (System.Byte[] buffer, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at Microsoft.Owin.Host.HttpListener.RequestProcessing.ExceptionFilterStream.Write (System.Byte[] buffer, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0

非常感谢任何帮助。先提前致谢!


克里斯,你解决了这个问题吗? - Sergey Moshkov
@Sergey,问题确实得到了解决,并且我已经部署到生产环境中,一直在公司工作几个月。很抱歉,我不记得是什么解决了它。我不知道Andre van der walt的答案是否解决了它,但他所说的是正确的。将Hub设置为静态是不可取的。 - Chris Ray
1
你是谁,@ChrisRay?你看到了什么?!;-) https://xkcd.com/979/ - Ben Burns
4个回答

5

3

我在树莓派上运行简单的Owin自托管演示应用程序时遇到了同样的问题。仅通过按住F5键从单个浏览器请求“欢迎”页面就足以在15秒内使主机崩溃。我还没有进行任何自动化压力测试,因为我的“用户验收”测试立即失败。

我找到的可靠解决方案是创建自定义OwinMiddleware来捕获和阻止套接字异常:

class CustomExceptionMiddleware : OwinMiddleware
{
    public CustomExceptionMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override async Task Invoke(IOwinContext context)
    {
        try
        {
            await Next.Invoke(context);
        }
        catch (IOException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

Owin启动程序需要更新以使用中间件:

using Owin;

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use<OwnExceptionMiddleware>()
           .UseNancy();
    }
}

这篇文章的灵感来自于http://dhickey.ie/2014/02/bubbling-exceptions-in-nancy-up-the-owin-pipeline/

以下是我参考的异常:

Unhandled Exception:
System.IO.IOException: Write failure ---> System.Net.Sockets.SocketException: The socket has been shut down
  at System.Net.Sockets.Socket.Send (System.Byte[] buf, Int32 offset, Int32 size, SocketFlags flags) [0x00000] in <filename unknown>:0
  at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in <filename unknown>:0
  --- End of inner exception stack trace ---
  at System.Net.Sockets.NetworkStream.Write (System.Byte[] buffer, Int32 offset, Int32 size) [0x00000] in <filename unknown>:0
  at System.Net.ResponseStream.InternalWrite (System.Byte[] buffer, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0
  at System.Net.ResponseStream.Close () [0x00000] in <filename unknown>:0
  at System.Net.HttpConnection.Close (Boolean force_close) [0x00000] in <filename unknown>:0
  at System.Net.HttpListenerResponse.Close (Boolean force) [0x00000] in <filename unknown>:0
  at System.Net.HttpListenerResponse.Abort () [0x00000] in <filename unknown>:0
  at Microsoft.Owin.Host.HttpListener.RequestProcessing.OwinHttpListenerResponse.End () [0x00000] in <filename unknown>:0
  at Microsoft.Owin.Host.HttpListener.RequestProcessing.OwinHttpListenerContext.End () [0x00000] in <filename unknown>:0
  at Microsoft.Owin.Host.HttpListener.RequestProcessing.OwinHttpListenerContext.End (System.Exception ex) [0x00000] in <filename unknown>:0
  at Microsoft.Owin.Host.HttpListener.OwinHttpListener+<ProcessRequestAsync>d__5.MoveNext () [0x00000] in <filename unknown>:0
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x00000] in <filename unknown>:0
  at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in <filename unknown>:0
  at Microsoft.Owin.Host.HttpListener.OwinHttpListener+<ProcessRequestsAsync>d__0.MoveNext () [0x00000] in <filename unknown>:0

在拦截异常时,考虑具体到哪些异常需要拦截:

    public override async Task Invoke(IOwinContext context)
    {
        try
        {
            await Next.Invoke(context);
        }
        catch (IOException ex)
        {
            if (ex.HResult == -2146233087) // The socket has been shut down
            {
                NLog.LogManager.GetLogger("OwinExceptionHandler").Trace(ex);
            }
            else
            {
                throw;
            }

        }
    }

3
这意味着每当发生断开连接时,Linux中的Mono会抛出与Windows中的MS.NET不同的异常。SignalR是由使用MS.NET的人员实现的,因此SignalR必须期望某些异常并严格遵守这些异常。
修复此问题的最佳方法是调试代码,跟踪SignalR在Linux上使用Mono时捕获异常的位置,并将其与Windows下使用MS.NET的SignalR发生的情况进行比较。然后创建一个有关差异的最小测试用例,并在 http://bugzilla.xamarin.com/ 上提交错误报告,他们可能很快就会解决它(或者您可以指定给我,因为我对在mono下运行SignalR感兴趣)。

你应该问 @chris-ray,他从未告诉我是否已经提交了这个错误报告...但是如果你也遇到了这个问题,你可以自己提交错误报告。 - knocte

2
我认为我有同样的问题。我已经描述了它: 我找到了解决方案。我在OWIN配置方法中将HttpListener.IgnoreWriteExceptions属性设置为“true”,然后注册中间件。例如:
internal class Startup
{
    public void Configuration(IAppBuilder app)
    {
        object httpListener;

        if (app.Properties.TryGetValue(typeof(HttpListener).FullName, out httpListener)
            && httpListener is HttpListener)
        {
            // HttpListener should not return exceptions that occur
            // when sending the response to the client
            ((HttpListener)httpListener).IgnoreWriteExceptions = true;
        }

        app.Use<TestOwinMiddleware>();
    }
}

我希望这能对您有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接