C# TCP套接字5分钟后CPU/内存使用率高

C# TCP套接字5分钟后CPU/内存使用率高,c#,sockets,memory-leaks,cpu-usage,tcpclient,C#,Sockets,Memory Leaks,Cpu Usage,Tcpclient,我正在构建一个接受传入TCP连接的服务器应用程序。(大约300个独特的客户端)。需要注意的是,我无法控制客户 我发现,在进行初始连接并发送第一次状态更新后,一些正在连接的客户端在相当长的一段时间内保持空闲状态。当它们保持空闲超过5分钟时,应用程序的CPU使用率将跃升到90%以上并保持不变 为了解决这个问题,我内置了一个4分钟后触发的取消令牌。这允许我终止连接。然后,客户端检测到这一点,并在大约一分钟后重新连接。这解决了CPU使用率高的问题,但有内存使用率高的副作用,似乎存在内存泄漏。我怀疑资源被

我正在构建一个接受传入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 void StartCommunication()
{
Task.Run(异步()=>
{
ArraySegment缓冲区=新的ArraySegment(新字节[75]);
while(断开连接)
{
尝试
{
//这就是我怀疑内存泄漏的来源-我怀疑当对象被关闭时,这个调用没有被正确清除
var result=await SocketTaskExtensions.ReceiveAsync(ClientConnection.Client,buffer,SocketFlags.None).WithCancellation(cts.Token);
如果(结果>0)
{
var message=newclientmessage(buffer.Array,true);
if(message.IsValid)
HandleClient消息(消息);
}
}
捕获(操作取消异常)
{
_closeConnection=true;
DisconnectReason=“客户端在4分钟内未报告”;
}
捕获(例外e)
{
_closeConnection=true;
DisconnectReason=“接收操作期间出错”;
}
}
});
}
公共空间处置()
{
处置(真实);
总干事(本);
}
受保护的虚拟void Dispose(bool disposing)
{
如果(处置)
{
_closeConnection=true;
cts.Cancel();
//显式终止底层套接字
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;
    }
取消的公共静态异步任务(此任务任务,取消令牌取消令牌)
{
var tcs=new TaskCompletionSource();
使用(cancellationToken.Register(s=>((TaskCompletionSource)s.TrySetResult(true),tcs))
{
if(task!=等待task.wheny(task,tcs.task))
{
抛出新操作CanceledException(cancellationToken);
}
}
返回任务。结果;
}
管理者代码:

    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();
        }
    }
public bool StartListener()
{
_侦听器=新的TcpListenerEx(IPAddress.Any,Convert.ToInt32(_serverPort));
_listener.Start();
Task.Run(异步()=>
{
而(_维护连接)//
{
尝试
{
//创建新的Coms对象
var client=new ClientComsAsync();
client.ClientConnection=client;
//启动客户端通信
client.StartCommunication();
//_客户端是ConcurrentDictionary
ClientComsAsync现有客户端;
if(_clients.TryGetValue(client.ClientName,out existingClient)&&existingClient!=null)
{
如果(existingClient.IsConnected)
existingClient.SendHeatbeat();
如果(!existingClient.IsConnected)
{
//对现有客户端调用Dispose
CleanUpClient(现有客户端,“重新连接到新连接”);
}
}
}
捕获(例外e)
{
//
}
最后
{
//
}
});
}
私有void CleanUpClient(ClientComsAsync客户端,字符串原因)
{
clientcomsasyncu客户;
_units.TryRemove(client.ClientName,out\u client);
如果(_client!=null)
{
_client.Dispose();
}
}
当它们保持空闲超过5分钟时,应用程序的CPU使用率将跃升到90%以上并保持不变

为了解决这个问题,我内置了一个4分钟后触发的取消令牌

正确的反应是解决高CPU使用率问题

在我看来,它就在这里:

while (IsConnected)
{
  try
  {
    var result = await SocketTaskExtensions.ReceiveAsync(ClientConnection.Client, buffer, SocketFlags.None);

    if (result > 0)
    {
      ...
    }
  }
  catch ...
  {
    ...
  }
}
套接字很奇怪,处理原始TCP/IP套接字非常困难。顺便说一句,我一直鼓励开发人员使用更标准的东西,如HTTP或WebSocket,但在这种情况下,您无法控制客户端,因此这不是一个选项

具体来说,您的代码没有处理
result==0
的情况。如果客户端设备优雅地关闭其套接字,您将看到
result
0
,立即循环