我们需要一个支持与许多客户端进行TCP通信的Windows服务 . 所以我把它 Build 在MSDN Async Example上 . 微软的例子就是客户端向服务器发送一条消息,然后服务器重新发送消息然后关闭 . 大!
因此,盲目地将此部署到我们的产品和客户网站,我们得到报告说它已经崩溃了 . 看看Prod,我们注意到在1天之后,在抛出OutOfMemoryException之前,内存使用量增长到不到1GB . 这里有很多测试!
这发生在1个连接的客户端 . 它发送一个基于XML的消息,每秒大约1200字节 . 是的,每一秒 .
然后,该服务进行一些处理并将返回XML消息发送回客户端 .
我已将TCP客户端/服务器通信转换为一组简单的控制台应用程序来复制问题 - 主要是为了消除其他托管/非托管资源 . 现在我已经看了好几天了,把我所有的头发和牙齿拉了出来 .
在我的例子中,我将重点关注以下类:
B2BSocketManager (Server Listener, Sender, Receiver)
注意我已经更改了代码以返回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 (Client Sender)
客户端每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());
}
}
}
}
State Class
我想要的只是一个读取缓冲区来消除消息并尝试加载到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];
}
}
我在这里分享了我的代码:
我一直使用我的Telerik JustTrace Profiler来分析事物 . 我刚启动服务器应用程序然后启动客户端应用程序 . 这是在我的Windows 7 64位VS2013开发环境中 .
Run 1
我看到内存使用量大约为250KB,工作集大约为20MB . 时间似乎很顺利,然后突然间内存使用将在大约12分钟后加速 . 虽然事情各不相同
在我强制GC的~16:45:55(快照)之后,每次按下它时内存开始上升而不是让它继续运行并自动升高,这可能是Telerik的一个问题 .
Run 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);
}
我们可以看到更多的内存:
这让我想到了记忆中保留的内容 . 我看到了System.Threading.OverlappedData的日志,我注意到了ExecutionContexts . OverlappedData这次引用了一个字节数组 .
使用Roots Paths to GC
我正在进行整夜的分析,所以希望能够在早上添加更多信息 . 希望有人能在此之前指出我正确的方向 - 如果我做错了什么,我太盲目/愚蠢地看到它 .
这是隔夜运行的结果: