我们需要一个支持与多个客户端进行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];
}
}
我在这里分享了我的代码:
我使用Telerik JustTrace Profiler进行了剖析。我刚启动服务器应用程序然后启动客户端应用程序。这是在我的Windows 7 64位VS2013开发环境中。
运行1
我看到内存使用量大约为250KB,工作集大约为20MB。时间似乎很顺利,然后突然间内存使用将在大约12分钟后加速。虽然情况各不相同。
在我强制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);
}
我们可以看到内存增加更多:
这让我想到了记忆中保留的东西。我看到了System.Threading.OverlappedData的日志,我注意到了ExecutionContexts。 OverlappedData这次引用了一个字节数组。
使用Roots Paths to GC
我正在进行整夜的分析,所以希望能在早上添加更多信息。希望有人能在此之前指出我正确的方向 - 如果我做错了什么而且我太盲目/愚蠢地看不到它。
以下是一夜之间的结果: