none
关于Socket高并发时出现的异常 RRS feed

  • 问题

  •     使用.Net Socket 的 SocketAsyncEventArgs 方式做服务端进行侦听接受客户端连接,当并发线程数小于50,且连接的客户端(工业设备)较为稳定不会时常断线时,客户端数量达到3万以上均无问题,且CPU及内存使用量都非常小。但是发现以下情况时服务端会出现崩溃:

    1、 并发线程数超过 50 时,会出现服务端无法接受新的客户端连接。

        经过反复实验发现,再次调用 SocketServer.AcceptAsync(args) 方法可以恢复接受连接,所以估计是 .Net Socket 内部处理问题,导致 args.Completed 事件没有产生。

    2、 连接到到服务端的设备大批量同时掉线时会出现以下异常,该异常无法捕获:

        System.Transactions Critical: 0 : <TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Critical"><TraceIdentifier>http://msdn.microsoft.com/TraceCodes/System/ActivityTracing/2004/07/Reliability/Exception/Unhandled</TraceIdentifier><Description>未处理的异常</Description><AppDomain>amschannelwin.vshost.exe</AppDomain><Exception><ExceptionType>System.NullReferenceException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType><Message>未将对象引用设置到对象的实例。</Message><StackTrace>   在 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)</StackTrace><ExceptionString>System.NullReferenceException: 未将对象引用设置到对象的实例。

        在 System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)</ExceptionString></Exception></TraceRecord>

    3、 某些工业设备可能未严格按照 TCP 协议进行握手、数据传输、断开连接等底层通讯,导致使用 .Net Socket 编写的服务端出现更为严重的错误,该错误也无法进行异常捕获:

        检测到 FatalExecutionEngineError
        Message: 运行时遇到了错误。此错误的地址为 0x793dfbf0,在线程 0x744 上。错误代码为 0xc0000005。此错误可能是 CLR 中的 bug,或者是用户代码的不安全部分或不可验证部分中的 bug。此 bug 的常见来源包括用户对 COM-interop 或 PInvoke 的封送处理错误,这些错误可能会损坏堆栈。

        以上是使用 C# 语言 .Net 4.0 开发 Socket 服务器遇到的问题,但是同样的工业设备连接到 C++ 开发的服务器上没有问题,希望能与 C# 开发遇到同样问题的朋友进行交流。

    2012年5月28日 16:30

答案

  • 补充:

    问题1 是使用C++编写的模拟软件进行测试,设置并发线程为50,在连接数达到2000 - 5000时均会出现无法接受连接的情况,而此时服务器程序的内存占用仅为50M。但是设置并发线程小于50后,可以稳定的达到3万连接,并且内存仅为60M,CPU负荷也很低,简化后的代码如下:

    this.SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    this.SocketServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
    this.SocketServer.SendBufferSize = 255;
    this.SocketServer.ReceiveBufferSize = 255;
    this.SocketServer.Bind(new IPEndPoint(System.Net.IPAddress.Any, 4002));
    this.SocketServer.Listen(500);
    this.Accept();

    private void Accept()
    {
        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += new EventHandler<SocketAsyncEventArgs>(SocketAsyncEventArgs_Completed);

        try
        {
            if (this.SocketServer.AcceptAsync(args) == false)
            {
                this.AcceptCallback(args);
            }
        }
        catch (Exception)
        {
            this.Accept();
        }
    }
    以上代码未使用栈来存放空闲SocketAsyncEventArgs以达到可复用的目的,发现入栈出栈更加消耗资源消耗时间

    问题2和问题3 均是在现场环境中出现的,使用模拟软件未出现过此异常,且现场环境中接入的客户端数量仅为2000不到,并且通讯量并不大,服务端压力也很小。怀疑还是 .Net 底层 Socket 处理的问题。

    2012年5月29日 5:10
  • 第一点当在很多连接接入的时候,如果处理不级时是会存在连接不能接入的情况,1时候可以通过listen指定队列数量,2AcceptAsync尽可以快速的处理进入下一下AcceptAsync,如果过程中有逻辑处理可以把accept的socket扔到队列中,然后马上开始2AcceptAsync.既然可以通过AcceptAsync恢复显然是你的逻辑代码处理问题导致没有执行AcceptAsync

    第二点触发这个错误是PerformIOCompletionCallback,这个方法是IO回调用的,应用是EndReceive或args.Completed 涉及的地方,估计是你程序问题可以看一exception的详细信息来排查.

    第三点也许真的是.net问题

    有一点更搞不懂既然是使用异步模型,为何需要用到这么多经程处理,即使你有3W个连接每秒每个连接都要进行发或接收处理,基本一个接收线和和一个发送线程就完全足够应付.

     
    2012年6月5日 13:06

全部回复

  • 补充:

    问题1 是使用C++编写的模拟软件进行测试,设置并发线程为50,在连接数达到2000 - 5000时均会出现无法接受连接的情况,而此时服务器程序的内存占用仅为50M。但是设置并发线程小于50后,可以稳定的达到3万连接,并且内存仅为60M,CPU负荷也很低,简化后的代码如下:

    this.SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    this.SocketServer.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
    this.SocketServer.SendBufferSize = 255;
    this.SocketServer.ReceiveBufferSize = 255;
    this.SocketServer.Bind(new IPEndPoint(System.Net.IPAddress.Any, 4002));
    this.SocketServer.Listen(500);
    this.Accept();

    private void Accept()
    {
        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        args.Completed += new EventHandler<SocketAsyncEventArgs>(SocketAsyncEventArgs_Completed);

        try
        {
            if (this.SocketServer.AcceptAsync(args) == false)
            {
                this.AcceptCallback(args);
            }
        }
        catch (Exception)
        {
            this.Accept();
        }
    }
    以上代码未使用栈来存放空闲SocketAsyncEventArgs以达到可复用的目的,发现入栈出栈更加消耗资源消耗时间

    问题2和问题3 均是在现场环境中出现的,使用模拟软件未出现过此异常,且现场环境中接入的客户端数量仅为2000不到,并且通讯量并不大,服务端压力也很小。怀疑还是 .Net 底层 Socket 处理的问题。

    2012年5月29日 5:10
  • 你好,

    这30000链接 其实都是由这些线程完成的, 所以只要操作系统能够处理的过来这些线程,那么这些链接应该就没问题。当线程太多,操作系统调度不过来时,可能就会出现问题。


    Ghost,
    Call me ghost for short, Thanks
    To get the better answer, it should be a better question.

    2012年5月29日 8:30
  • 作为服务器应用,即使操作系统调度不过来,也不应出现严重故障。如果处理不过来,数据被丢弃也比出现故障的说法要好
    2012年5月30日 1:05
  • 所以 像你说的, 少一点线程不是很好么


    Ghost,
    Call me ghost for short, Thanks
    To get the better answer, it should be a better question.

    2012年5月30日 5:57
  • 设备的连接不是我能控制的,特别是当服务器重启后,大量的设备同时连接服务器,用C++完成端口开发的服务器可以扛得住这样的压力,而.Net的异步套接字却会出现各种错误。

    如果.Net的异步套接字可以保持稳定性,即使性能略差于C++,我们还是希望可以使用.Net进行服务端的开发,整套系统语言统一对系统的可维护性、延续性等方面的好处是不言而喻的。

    2012年5月30日 10:53
  • 你也知道,.net都是由CLR负责运行,这里面可能是你的代码处理不当,不过我觉得我是看不出,不好意思。


    Ghost,
    Call me ghost for short, Thanks
    To get the better answer, it should be a better question.

    2012年5月31日 3:48
  • 兄弟给你个注意,直接向微软的开发人员问。有答案了别忘了给大伙说说

    Mariya Atanasova是 System.Net 团队测试部的一名软件设计工程师。您可以通过她的博客 blogs.msdn.com/mariya 与她取得联系。


    Larry Cleeton是 System.Net 团队的一名软件设计工程师。您可以通过他的博客 blogs.msdn.com/lcleeton 与他取得联系。


    Mike Flasko是 System.Net、Winsock 和 Winsock Kernel 团队的项目经理。您可以通过他的博客 blogs.msdn.com/mflasko 与他取得联系。


    Amit Paka是 System.Net 团队的一名软件设计工程师。您可以通过他的博客 blogs.msdn.com/amitpaka 与他取得联系。
    2012年6月2日 1:59
  • 可能是socket listen 的时候设置的队列太小

                _socket.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 5555));
                _socket.Listen(1000);

    话说如果是做对象池  4.0 用 concurrent bag 代替 locked stack  效率会高很多

    listen的其实用不到 对象池 用了也浪费   只有 send receive 需要 而且租期与连接的生命周期要一致    一次receive 就要拿一次资源 那肯定效率低下


    入了ipad,最近用ipad上论坛

    2012年6月2日 22:16
    版主
  • 楼主,请直接向微软发送求救信息!

    msdnmg@microsoft.com

    2012年6月3日 1:27
  • 第一点当在很多连接接入的时候,如果处理不级时是会存在连接不能接入的情况,1时候可以通过listen指定队列数量,2AcceptAsync尽可以快速的处理进入下一下AcceptAsync,如果过程中有逻辑处理可以把accept的socket扔到队列中,然后马上开始2AcceptAsync.既然可以通过AcceptAsync恢复显然是你的逻辑代码处理问题导致没有执行AcceptAsync

    第二点触发这个错误是PerformIOCompletionCallback,这个方法是IO回调用的,应用是EndReceive或args.Completed 涉及的地方,估计是你程序问题可以看一exception的详细信息来排查.

    第三点也许真的是.net问题

    有一点更搞不懂既然是使用异步模型,为何需要用到这么多经程处理,即使你有3W个连接每秒每个连接都要进行发或接收处理,基本一个接收线和和一个发送线程就完全足够应付.

     
    2012年6月5日 13:06
  • 貌似需要定时重启下服务端程序,不然链接变得很不稳定
    2014年5月27日 8:32