.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<Thread, Context> _into_rrc = new ConcurrentDictionary<Thread, Context>();
        [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<ThreadExceptionEventArgs> 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);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在VB.NET中,可以使用TCP/IP协议来进行客户端的发送和接收操作。 要创建一个TCP/IP客户端,首先我们需要导入`System.Net.Sockets`命名空间,并创建一个`TcpClient`实例,这个实例可以用来与服务器进行通信。 假设我们已经知道服务器的IP地址和端口号,我们可以使用`TcpClient`实例的`Connect`方法连接到服务器。例如: ```vb Dim client As New TcpClient() client.Connect("服务器IP地址", 端口号) ``` 接下来,我们可以使用`TcpClient`实例的`GetStream`方法获得一个`NetworkStream`实例。`NetworkStream`类用于在网络上发送和接收数据。 ```vb Dim stream As NetworkStream = client.GetStream() ``` 现在我们可以使用`NetworkStream`实例的`Write`方法发送数据到服务器。例如,发送一个字符串: ```vb Dim data As String = "Hello, server!" Dim bytes As Byte() = Encoding.ASCII.GetBytes(data) stream.Write(bytes, 0, bytes.Length) ``` 同样,我们也可以使用`NetworkStream`实例的`Read`方法从服务器接收数据。例如,接收服务器发来的回应: ```vb Dim buffer As Byte() = New Byte(client.ReceiveBufferSize - 1) {} Dim bytesRead As Integer = stream.Read(buffer, 0, buffer.Length) Dim responseData As String = Encoding.ASCII.GetString(buffer, 0, bytesRead) ``` 最后,我们可以关闭`NetworkStream`和`TcpClient`实例,释放资源: ```vb stream.Close() client.Close() ``` 这样,我们就完成了使用VB.NET进行TCP/IP客户端的发送和接收操作。请注意,在实际应用中,可能需要处理更多的异常和错误情况,并进行适当的错误处理和重试。 ### 回答2: TCP/IP是一种广泛应用于计算机网络通信的协议。在VB.NET中,我们可以使用TcpClient类来创建一个TCP/IP客户端,并进行发送和接收数据的操作。 首先,我们需要在VB.NET项目中引入System.Net命名空间,以便使用TcpClient类。然后,我们可以通过实例化TcpClient类来创建一个TCP/IP客户端对象。 创建对象的代码如下: Dim client As New TcpClient 然后,我们可以使用TcpClient类的Connect方法来连接到服务器。Connect方法接受一个IP地址和端口号作为参数。 连接服务器的代码示例如下: client.Connect("服务器IP地址", 端口号) 连接成功后,我们就可以使用TcpClient类的GetStream方法来获取数据流。数据流提供了发送和接收数据的功能。 获取数据流的代码示例如下: Dim stream As NetworkStream = client.GetStream() 发送数据非常简单,我们可以使用数据流的Write方法将数据发送到服务器。Write方法接受一个字节数组作为参数,将字节数组中的数据发送出去。 发送数据的代码示例如下: Dim data As Byte() = Encoding.ASCII.GetBytes("需要发送的数据") stream.Write(data, 0, data.Length) 接收数据也很简单,我们可以使用数据流的Read方法从服务器接收数据。Read方法接受一个字节数组作为参数,并将接收到的数据存储在字节数组中。 接收数据的代码示例如下: Dim buffer As Byte() = New Byte(1023) {} Dim bytesRead As Integer = stream.Read(buffer, 0, buffer.Length) Dim receivedData As String = Encoding.ASCII.GetString(buffer, 0, bytesRead) 最后,我们可以使用TcpClient类的Close方法关闭TCP/IP客户端连接。 关闭连接的代码示例如下: client.Close() 通过以上步骤,我们可以在VB.NET中创建一个TCP/IP客户端,并进行发送和接收数据的操作。 ### 回答3: VB.NET中实现TCP/IP客户端的发送和接收可以通过使用Socket类来完成。下面是一个简单的示例: 首先,在VB.NET中引入System.Net.Sockets命名空间。 ``` Imports System.Net.Sockets ``` 然后,创建一个TcpClient对象来建立与服务器的连接。 ``` Dim client As TcpClient = New TcpClient() client.Connect("服务器IP地址", 服务器端口号) ``` 接下来,可以使用网络流来发送和接收数据。 发送数据: ``` Dim data As String = "要发送的数据" Dim stream As NetworkStream = client.GetStream() Dim bytes As Byte() = Encoding.UTF8.GetBytes(data) stream.Write(bytes, 0, bytes.Length) ``` 接收数据: ``` Dim buffer(1024) As Byte Dim bytesRead As Integer = stream.Read(buffer, 0, buffer.Length) Dim receivedData As String = Encoding.UTF8.GetString(buffer, 0, bytesRead) ``` 最后,关闭连接。 ``` stream.Close() client.Close() ``` 这就是用VB.NET实现TCP/IP客户端的发送和接收的基本步骤。当然,在实际应用中可能需要添加一些错误处理和数据处理的逻辑,以上仅为简单示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值