.NET TCP/IP Socket 异步循环收取导致栈溢出问题

我们知道 .NET TCP/IP Socket 异步循环收取官方及绝大多数例子都是循环收取的,但实际上这个方法存在栈溢出的问题。

MD,收到收到堆栈溢出程序直接就崩了,目前解决的方案有几个路子,Stack Overflow 有老外提出的几个路子都特别野,我先说老外提的一个路子的优点,它的确解决了 .NET TCP/IP Socket 异步一直循环收取会导致栈溢出的问题,但是IO吞吐量都不怎么上的去了呀,而且对CPU的负担变得非常大。

它们提出的方法就是:本次收取完毕的时候投递拉取下个收取由 ThreadPool 来完成发起,虽然我们知道 ThreadPool 是基于 IoCP 完成端口模型来实现的,性能是非常高,但是在性能高,能有当前线程递归执行收取高?先不说少走多少代码执行,光就一点CPU线程上下文切换、系统调度是不要硬件成本的啊!

尤其是这种大规模并发的时候,对CPU的负担那叫一个大,所以这就是我说的为什么这个路子,它有点野的缘故,要是个单核垃圾VPS服务器上面跑这样,我们想尽量得发挥程序性能,拉到最大的IO吞吐量,岂不是就架空了,所以这个方法是,绝对,绝对不得行的。

之所以会有这个问题是因为,.NET TCP/IP Socket 的收取实现路子并不是当前线程执行 Socket 的 BeginReceive 函数,就一定是当前线程调用执行完 BeginReceive 函数并返回以后,才触发 AsyncCallback,你懂的,不是的?

当前线程还在执行 BeginReceive 过程中,它就回调收取到达了,而回调的线程还是当前线程,然而我们还需要在回调函数里面继续的收取,这就产生了递归的问题,它要是递归几层然后由其它线程来完成执行 EndReceive 还好,可怕的是在某个TCP/IP连接大量的传入数据的情况,会导致收取函数一直递归执行,最后只有一个结果:堆栈爆炸,程序崩溃。

.NET Socket::BeginReceive 函数,会先执行 WSARecv 函数尝试收取BUFF到应用程序,如果可以从系统PULL到BUFF没有错误则直接回调,否则等待系统通知IOCP SOCKET RECV完成,IOCP队列工作线程在回调,问题就出在这里,.NET Core 跨平台 Linux 上面也存在相同的问题,好的那么这个时候,我们应该怎么办比较好?

上面提到了老外提出的一个方法,还有另外一个也不是怎么靠谱的办法就是判断 AsyncCallback 回调调用时候传入的 IAsyncResult 对象上面的 CompletedSynchronously 属性,然后决定是否需要由把下次收取丢到 ThreadPool 队列去完成,本质上其实没多大区别,仍旧存在类似的问题,只是会好一点。

那么提出我个人适用的方法,这个路子兼顾性能又能解决循环收取堆栈溢出的问题,即我们仍旧让 Socket 可以循环递归收取,但这是有限度的并不是无限制的递归,不然堆栈肯定要爆,递归到一定层数就需要委托到 ThreadPool 由其它线程来执行下次异步收取。

根据我们目前的测试定义可递归堆栈层数在100层是比较好的,Windows/Linux 都能通用,100层递归基本可以做的跟一直递归产生的吞吐量差不多,而且对CPU的负担也没有每次收取都丢到线程池队列来完成那么巨大。

但是管理堆栈可以步入多少层,我们不能在每个类/对象单独管理,而是需要在一个统一类/对象上面进行管理,因为考虑到通用性,万一某些程序搞得一个线程上面其实已经重叠了很多次 TCP/IP Socket 的收取调用堆栈序列,仍旧可能会导致栈溢出的问题。

例子:

        private readonly ThreadProtection server_thread_protection = new ThreadProtection();

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        [SecurityCritical]
        [SecuritySafeCritical]
        private void PullServerListener(IAsyncResult ar) => this.server_thread_protection.Execute((_) =>
        {
            byte[] buffer = this.server_buffer;
            if (ar == null)
            {
                if (!SocketExtension.BeginReceive(this.server, buffer, 0, MSS, this.PullServerListener))
                {
                    this.Dispose();
                }
            }
            else
            {
                bool disposing = true;
                int count = SocketExtension.EndReceive(this.server, ar);
                if (count > 0)
                {
                    try
                    {
                        if (this.ProcessServerInput(buffer, 0, count))
                        {
                            disposing = false;
                        }
                    }
                    catch (Exception) { }
                }
                if (disposing)
                {
                    this.Dispose();
                }
            }
        });

ThreadProtection.cs 类得具体实现

namespace My.Threading
{
    using System;
    using System.Collections.Concurrent;
    using System.Diagnostics;
#if NETCOREAPP
    using System.Runtime.CompilerServices;
#endif
    using System.Security;
    using System.Threading;

    public sealed class ThreadProtection : IDisposable
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static readonly ConcurrentDictionary _into_rrc = new ConcurrentDictionary();
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static readonly Timer _into_rrc_timer = null;
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private static readonly object _globalsync = new object();

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly object _syncobj = new object();
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Thread _into_thread = null;

        public const int MaxRecursiveLayers = 100;

        private sealed class Context
        {
            public int rrc = 0;
        }

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        [SecurityCritical]
        [SecuritySafeCritical]
        static ThreadProtection()
        {
            _into_rrc_timer = new Timer();
            _into_rrc_timer.Interval = 1000;
            _into_rrc_timer.Tick += (sender, e) =>
            {
                foreach (var kv in _into_rrc)
                {
                    Thread thread = kv.Key;
                    if (!thread.IsAlive)
                    {
                        _into_rrc.TryRemove(thread, out Context context);
                    }
                }
            };
            _into_rrc_timer.Start();
        }

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        [SecurityCritical]
        [SecuritySafeCritical]
        public ThreadProtection() : this(MaxRecursiveLayers)
        {

        }

        ~ThreadProtection() => this.Dispose();

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        [SecurityCritical]
        [SecuritySafeCritical]
        public ThreadProtection(int maxInto)
        {
            if (maxInto < MaxRecursiveLayers)
            {
                maxInto = MaxRecursiveLayers;
            }
            this.MaximumInto = maxInto;
        }

        public event EventHandler UnhandledException;

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        private static Context GetContext()
        {
            lock (_globalsync)
            {
                Thread thread = Thread.CurrentThread;
                _into_rrc.TryGetValue(thread, out Context context);
                if (context == null)
                {
                    context = new Context();
                    _into_rrc[thread] = context;
                }
                return context;
            }
        }

        public int CurrentInto
        {
#if NETCOREAPP
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
            get => Interlocked.CompareExchange(ref GetContext().rrc, 0, 0);
        }

        public int MaximumInto
        {
#if NETCOREAPP
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
            get;
        }

        public Thread IntoThread
        {
#if NETCOREAPP
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
            get => this._into_thread;
        }

        public Thread CurrentThread
        {
#if NETCOREAPP
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
            get => Thread.CurrentThread;
        }

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public void Execute(WaitCallback critical) => this.Execute(critical, null);

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        [SecurityCritical]
        [SecuritySafeCritical]
        public void Execute(WaitCallback critical, object state)
        {
            if (critical == null)
            {
                throw new ArgumentNullException(nameof(critical));
            }
            bool can_into = false;
            Thread current_thread = Thread.CurrentThread;
            Context current_context = GetContext();
            lock (this._syncobj)
            {
                Thread into_thread = Interlocked.CompareExchange(ref this._into_thread, null, current_thread);
                if (into_thread != current_thread)
                {
                    Interlocked.Exchange(ref current_context.rrc, 0);
                }
                can_into = this.MaximumInto >= Interlocked.Increment(ref current_context.rrc);
                if (!can_into)
                {
                    Interlocked.Exchange(ref current_context.rrc, 0);
                }
            }
            if (can_into)
            {
                try
                {
                    critical(state);
                }
                catch (Exception e)
                {
                    this.OnUnhandledException(e);
                }
            }
            else
            {
                WaitCallback into_callback = (input_state) =>
                {
                    try
                    {
                        critical(input_state);
                    }
                    catch (Exception e)
                    {
                        this.OnUnhandledException(e);
                    }
                };
                ThreadPool.QueueUserWorkItem(into_callback, state);
            }
        }

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        private void OnUnhandledException(Exception e)
        {
            if (e == null)
            {
                return;
            }
            ThreadExceptionEventArgs p = new ThreadExceptionEventArgs(e);
            try
            {
                this.UnhandledException?.Invoke(this, p);
            }
            catch (Exception) { }
        }

#if NETCOREAPP
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public void Dispose()
        {
            Interlocked.Exchange(ref this.UnhandledException, null);
            GC.SuppressFinalize(this);
        }
    }
}

你可能感兴趣的:(.NET,.net,tcp/ip,microsoft)