【发布时间】:2019-01-31 10:06:10
【问题描述】:
我正在构建一个接受传入 TCP 连接的服务器应用程序。 (大约 300 个独特的客户)。请务必注意,我无法控制客户端。
我发现一些正在连接的客户端在建立初始连接并发送第一个状态更新后会保持空闲一段时间。当它们保持空闲超过 5 分钟时,应用程序的 CPU 使用率会跃升至 90% 以上并保持在那里。
为了解决这个问题,我构建了一个 4 分钟后触发的取消令牌。这使我可以终止连接。然后客户端检测到这一点并在大约一分钟后重新连接。这解决了 CPU 使用率高的问题,但有高内存使用率的副作用,似乎存在内存泄漏。我怀疑资源被前一个套接字对象持有。
我有一个客户端对象,其中包含套接字连接和有关已连接客户端的信息。它还管理传入的消息。还有一个接受传入连接的管理器类。然后它创建客户端对象,为其分配套接字并将客户端对象添加到并发字典中。它每 10 秒检查一次字典中是否已设置为 _closeConnection = true 的客户端并调用它们的 dispose 方法。
这里是一些客户端对象代码:
public void StartCommunication()
{
Task.Run(async () =>
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[75]);
while (IsConnected)
{
try
{
// This is where I suspect the memory leak is originating - this call I suspect is not properly cleaned up when the object is diposed
var result = await SocketTaskExtensions.ReceiveAsync(ClientConnection.Client, buffer, SocketFlags.None).WithCancellation(cts.Token);
if (result > 0)
{
var message = new ClientMessage(buffer.Array, true);
if(message.IsValid)
HandleClientMessage(message);
}
}
catch (OperationCanceledException)
{
_closeConnection = true;
DisconnectReason = "Client has not reported in 4 mins";
}
catch (Exception e)
{
_closeConnection = true;
DisconnectReason = "Error during receive opperation";
}
}
});
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_closeConnection = true;
cts.Cancel();
// Explicitly kill the underlying socket
if (UnitConnection.Client != null)
{
UnitConnection.Client.Close();
}
UnitConnection.Close();
cts.Dispose();
}
}
任务扩展方法:
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
{
if (task != await Task.WhenAny(task, tcs.Task))
{
throw new OperationCanceledException(cancellationToken);
}
}
return task.Result;
}
经理代码:
public bool StartListener()
{
_listener = new TcpListenerEx(IPAddress.Any, Convert.ToInt32(_serverPort));
_listener.Start();
Task.Run(async () =>
{
while (_maintainConnection) // <--- boolean flag to exit loop
{
try
{
HandleClientConnection(await _listener.AcceptTcpClientAsync());
}
catch (Exception e)
{
//<snip>
}
}
});
return true;
}
private void HandleClientConnection(TcpClient client)
{
Task.Run(async () =>
{
try
{
// Create new Coms object
var client = new ClientComsAsync();
client.ClientConnection = client;
// Start client communication
client.StartCommunication();
//_clients is the ConcurrentDictionary
ClientComsAsync existingClient;
if (_clients.TryGetValue(client.ClientName, out existingClient) && existingClient != null)
{
if (existingClient.IsConnected)
existingClient.SendHeatbeat();
if (!existingClient.IsConnected)
{
// Call Dispose on existing client
CleanUpClient(existingClient, "Reconnected with new connection");
}
}
}
catch (Exception e)
{
//<snip>
}
finally
{
//<snip>
}
});
}
private void CleanUpClient(ClientComsAsync client, string reason)
{
ClientComsAsync _client;
_units.TryRemove(client.ClientName, out _client);
if (_client != null)
{
_client.Dispose();
}
}
【问题讨论】:
-
这个问题似乎更适合CodeReview 网站。由于您的代码正在运行,但您遇到了性能问题
-
analyze CPU usage with Windows Performance Toolkit 也许它表明这是呼叫造成的。
-
@magicandre1981 我知道导致 CPU 使用率高的原因。它的 ReceiveAsync 调用等待响应时间过长。这就是为什么我用令牌取消它,但后来我得到内存泄漏。我想知道如何处理泄漏。
标签: c# sockets memory-leaks cpu-usage tcpclient