【问题标题】:SignalR Owin Self-Host on Linux/Mono SocketException when clients lose connection当客户端失去连接时,Linux/Mono SocketException 上的 SignalR Owin Self-Host
【发布时间】:2015-12-10 12:36:46
【问题描述】:

我正在通过 Owin 在 ubuntu 服务器 14.04、单声道 3.2.8 上运行非常简单的自托管信号器服务器。 (代码如下)。

连接/断开连接在远程 Windows 服务器上以及当我将这些位部署到 linux 服务器时都可以正常工作。但是当一个客户端意外死亡而不是告诉信号器他正在断开连接时,那是我只在 linux 服务器上得到一个永无止境的 SocketException 的时候。 Windows 服务器大约 30 秒左右后断开客户端,但 linux 服务器每隔 10 秒左右喷出 socketexception(也在下面),永远。

如何让 linux 服务器在运行相同代码时表现得像 windows 服务器一样,在设置的超时后断开用户连接并且不抛出套接字异常?

服务器代码:

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
        }
    }
}

永无止境的单声道异常:

{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 问题确实得到了解决,我部署到生产环境中,它已经在生产环境中工作了几个月,直到我离开公司。对不起,我不记得是什么解决了它。我不知道 Andre van der walt 的回答是否解决了它,但他说的是正确的。将 Hub 设为静态是禁忌
  • 你是谁@ChrisRay?你看见什么了?! ;-) xkcd.com/979

标签: c# linux mono signalr socketexception


【解决方案1】:

问题在于您的启动方法中的这一行static Hub hub,以及这一行hub = new MyHub()

您不需要显式创建 Hub 类的实例,也不需要保留引用。对服务器的每个请求都会实例化集线器类。看 http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#transienceHub 对象生命周期部分。

【讨论】:

    【解决方案2】:

    这意味着每当出现断开连接时,Linux 中的 Mono 会抛出与 Windows 中的 MS.NET 不同的异常。 SignalR 是由最有可能使用 MS.NET 的人实现的,因此 SignalR 必须期待某些异常并且对此保持虔诚。

    解决此问题的最佳方法是调试进入 SignalR 实现的代码(在使用 Mono 的 Linux 上运行时)以查看在哪里捕获了异常,并与 Windows 中 MS.NET 下 SignalR 中发生的情况进行比较。然后创建一个关于差异的最小测试用例并在http://bugzilla.xamarin.com/ 中提交一个错误,他们可能会很快修复它(或者你可以将它分配给我,我会看看它,因为我有兴趣在单声道下运行 SignalR)。

    【讨论】:

    • 你应该问@chris-ray,他从来没有告诉我他是否提交了这个错误......但或者,如果你也遇到它,你可以自己提交
    【解决方案3】:

    我在 Raspberry Pi 上使用简单的 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;
                }
    
            }
        }
    

    【讨论】:

      【解决方案4】:

      我想我也有同样的问题。我是这样描述的:

      我找到了适合我情况的解决方案。我在 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>();
          }
      }
      

      希望对你有帮助。

      【讨论】:

        猜你喜欢
        • 2016-02-02
        • 1970-01-01
        • 2012-09-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多