.NET 4.5 ASync TCP服务器内存泄漏 - BeginReceive / BeginSend

时间:2014-06-17 16:01:17

标签: sockets asynchronous tcp

我们需要一个支持与多个客户端进行TCP通信的Windows服务。所以我基于MSDN Async Example微软的例子就是客户端向服务器发送一条消息,然后服务器重新发送消息然后关闭。太好了!

因此,盲目地将此部署到我们的产品和客户网站上,我们得到报告说它已经崩溃了。看看Prod,我们注意到在1天之后,在抛出OutOfMemoryException之前,内存使用量增长到不到1GB。这里有很多测试!

这发生在连接了1个客户端。它发送一个基于XML的消息,每秒大约1200字节。是的,每一秒。

然后,该服务进行一些处理并将返回的XML消息发送回客户端。

我已将TCP客户端/服务器通信拉入一组简单的控制台应用程序以复制问题 - 主要是为了消除其他托管/非托管资源。现在我已经看了好几天了,把我所有的头发和牙齿拉了出来。

在我的例子中,我专注于以下课程:

B2BSocketManager(服务器侦听器,发件人,接收者)

注意我已经更改了代码以返回whoopsy readonly字节数组 - 而不是已发送的消息。我还从BeginReceive / BeginSend调用中删除了新的AsyncCallback(委托)。

namespace Acme.OPC.Service.Net.Sockets
{
    using Acme.OPC.Service.Logging;
    using System;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    public class B2BSocketManager : ISocketSender
    {
        private ManualResetEvent allDone = new ManualResetEvent(false);
        private IPEndPoint _localEndPoint;
        private readonly byte[] whoopsy = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        public B2BSocketManager(IPAddress address, int port)
        {
            _localEndPoint = new IPEndPoint(address, port);
        }

        public void StartListening()
        {
            StartListeningAsync();
        }

        private async Task StartListeningAsync()
        {
            await System.Threading.Tasks.Task.Factory.StartNew(() => ListenForConnections());
        }

        public void ListenForConnections()
        {
            Socket listener = new Socket(_localEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            Log.Instance.Info("B2BSocketManager Listening on " + _localEndPoint.Address.ToString() + ":" + _localEndPoint.Port.ToString());

            try
            {
                listener.Bind(_localEndPoint);
                listener.Listen(100);

                while (true)
                {
                    allDone.Reset();

                    Log.Instance.Info("B2BSocketManager Waiting for a connection...");
                    listener.BeginAccept(new AsyncCallback(ConnectCallback), listener);
                    allDone.WaitOne();
                }
            }
            catch (Exception e)
            {
                Log.Instance.Info(e.ToString());
            }
        }

        public void ConnectCallback(IAsyncResult ar)
        {
            allDone.Set();

            Socket listener = (Socket)ar.AsyncState;
            Socket handler = listener.EndAccept(ar);
            handler.DontFragment = false;
            handler.ReceiveBufferSize = ClientSocket.BufferSize;

            Log.Instance.Info("B2BSocketManager Client has connected on " + handler.RemoteEndPoint.ToString());

            ClientSocket state = new ClientSocket();
            state.workSocket = handler;

            handler.BeginReceive(state.buffer, 0, ClientSocket.BufferSize, 0, new AsyncCallback(ReadCallback), state); // SocketFlags.None
        }

        public void ReadCallback(IAsyncResult ar)
        {
            String message = String.Empty;

            ClientSocket state = (ClientSocket)ar.AsyncState;
            Socket handler = state.workSocket;

            int bytesRead = handler.EndReceive(ar);
            if (bytesRead > 0)
            {
                Console.WriteLine("Received " + bytesRead + " at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));

                message = Encoding.ASCII.GetString(state.buffer, 0, bytesRead);

                if (!string.IsNullOrEmpty(message))
                {
                    Send(handler, message);
                }

                handler.BeginReceive(state.buffer, 0, ClientSocket.BufferSize, 0, ReadCallback, state);
            }
        }

        public void Send(Socket socket, string data)
        {
            // just hard coding the whoopse readonly byte array
            socket.BeginSend(whoopsy, 0, whoopsy.Length, 0, SendCallback, socket);
        }

        private void SendCallback(IAsyncResult ar)
        {
            Socket state = (Socket)ar.AsyncState;

            try
            {
                int bytesSent = state.EndSend(ar);
            }
            catch (Exception e)
            {
                Log.Instance.ErrorException("", e);
            }
        }
    }
}

ClientSender(客户端发件人)

客户端每250毫秒向服务器发送一个xml字符串。我想看看它会如何表现。 xml略小于我们在实时系统上发送的内容,只是使用格式化字符串创建。

namespace TestHarness
{
    using System;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;

    class ClientSender
    {
        private static ManualResetEvent connectDone = new ManualResetEvent(false);
        private static ManualResetEvent receiveDone = new ManualResetEvent(false);
        private static ManualResetEvent sendDone = new ManualResetEvent(false);

        private static void StartSpamming(Socket client)
        {
            while(true)
            {
                string message = @"<request type=""da"">{0}{1}</request>" + Environment.NewLine;

                Send(client, string.Format(message, "Be someone" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), String.Concat(Enumerable.Repeat("<test>Oooooooops</test>", 50))));

                Thread.Sleep(250);
            }
        }

        public static void Connect(EndPoint remoteEP)
        {
            Socket listener = new Socket(remoteEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listener.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), listener);

            connectDone.WaitOne();
        }

        private static void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;

                // Complete the connection.
                client.EndConnect(ar);

                Console.WriteLine("Socket connected to {0}", client.RemoteEndPoint.ToString());

                // Signal that the connection has been made.
                connectDone.Set();

                System.Threading.Tasks.Task.Factory.StartNew(() => StartSpamming(client));
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private static void Send(Socket client, String data)
        {
            byte[] byteData = Encoding.ASCII.GetBytes(data);
            client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), client);
        }

        private static void SendCallback(IAsyncResult ar)
        {
            try
            {
                Socket client = (Socket)ar.AsyncState;
                int bytesSent = client.EndSend(ar);
                Console.WriteLine("Sent {0} bytes to server " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), bytesSent);
                sendDone.Set();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private static void Receive(Socket client)
        {
            try
            {
                StateObject state = new StateObject();
                state.workSocket = client;

                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private static void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                StateObject state = (StateObject)ar.AsyncState;
                Socket client = state.workSocket;
                int bytesRead = client.EndReceive(ar);
                if (bytesRead > 0)
                {
                    state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
                }
                else
                {
                    if (state.sb.Length > 1)
                    {
                        string response = state.sb.ToString();
                    }
                    receiveDone.Set();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

州级

我想要的只是一个读取缓冲区,用于删除消息并尝试加载到XML中。但是这个已经从这个减少的版本中删除了,只看到套接字的问题。

using System;
using System.Linq;
using System.Net.Sockets;

namespace Acme.OPC.Service.Net.Sockets
{
    public class ClientSocket
    {
        public Socket workSocket = null;
        public const int BufferSize = 4096;
        public readonly byte[] buffer = new byte[BufferSize];
    }
}

我在这里分享了我的代码:

Explore One Drive Share

我使用Telerik JustTrace Profiler进行了剖析。我刚启动服务器应用程序然后启动客户端应用程序。这是在我的Windows 7 64位VS2013开发环境中。

运行1

我看到内存使用量大约为250KB,工作集大约为20MB。时间似乎很顺利,然后突然间内存使用将在大约12分钟后加速。虽然情况各不相同。

Profiler

在我强制GC后~16:45:55(快照)之后,每次按下它时内存开始上升而不是让它继续运行并自动升起,这可能是Telerik的一个问题

运行2

然后,如果我在Send with中创建字节数组(这更像是服务的作用 - 向客户端发送适当的响应字符串):

public void Send(Socket socket, string data)
{
    byte[] byteData = Encoding.ASCII.GetBytes(data);
    socket.BeginSend(byteData, 0, byteData.Length, 0, SendCallback, socket);
}

我们可以看到内存增加更多:

Run 2

这让我想到了记忆中保留的东西。我看到了System.Threading.OverlappedData的日志,我注意到了ExecutionContexts。 OverlappedData这次引用了一个字节数组。

Run 2 Largest Mem Retainers

使用Roots Paths to GC

Root Paths to GC

我正在进行整夜的分析,所以希望能在早上添加更多信息。希望有人能在此之前指出我正确的方向 - 如果我做错了什么而且我太盲目/愚蠢地看不到它。

以下是一夜之间的结果: Run overnight

0 个答案:

没有答案