C# TCP服务器连接导致处理器过载

C# TCP服务器连接导致处理器过载,c#,.net,tcp,tcplistener,C#,.net,Tcp,Tcplistener,我有一个TCP/IP服务器,当消息通过它发送时,它应该允许连接保持打开状态。然而,似乎有些客户机为每条消息打开了一个新的连接,这导致CPU使用量达到最大值。我试图通过添加一个超时来解决这个问题,但似乎偶尔也会出现问题。我怀疑我的解决方案不是最好的选择,但我不确定会是什么 下面是我删除日志记录、错误处理和处理的基本代码 private void StartListening() { try { _tcpListener = new TcpLi

我有一个TCP/IP服务器,当消息通过它发送时,它应该允许连接保持打开状态。然而,似乎有些客户机为每条消息打开了一个新的连接,这导致CPU使用量达到最大值。我试图通过添加一个超时来解决这个问题,但似乎偶尔也会出现问题。我怀疑我的解决方案不是最好的选择,但我不确定会是什么

下面是我删除日志记录、错误处理和处理的基本代码

private void StartListening()
{            
    try
    {
        _tcpListener = new TcpListener( IPAddress.Any, _settings.Port );
        _tcpListener.Start();
        while (DeviceState == State.Running)
        {
            var incomingConnection = _tcpListener.AcceptTcpClient();
            var processThread = new Thread( ReceiveMessage );
            processThread.Start( incomingConnection );
        }
    }
    catch (Exception e)
    {
        //  Unfortunately, a SocketException is expected when stopping AcceptTcpClient
        if (DeviceState == State.Running) { throw; }
    }
    finally { _tcpListener?.Stop(); }
}
我认为实际的问题是正在创建多个进程线程,但没有关闭。下面是ReceiveMessage的代码

    private void ReceiveMessage( object IncomingConnection )
    {
        var buffer = new byte[_settings.BufferSize];
        int bytesReceived = 0;
        var messageData = String.Empty;
        bool isConnected = true;

        using (TcpClient connection = (TcpClient)IncomingConnection)
        using (NetworkStream netStream = connection.GetStream())
        {
            netStream.ReadTimeout = 1000;
            try
            {
                while (DeviceState == State.Running && isConnected)
                {
                    //  An IOException will be thrown and captured if no message comes in each second. This is the
                    //  only way to send a signal to close the connection when shutting down. The exception is caught,
                    //  and the connection is checked to confirm that it is still open. If it is, and the Router has
                    //  not been shut down, the server will continue listening.
                    try { bytesReceived = netStream.Read( buffer, 0, buffer.Length ); }
                    catch (IOException e)
                    {
                        if (e.InnerException is SocketException se && se.SocketErrorCode == SocketError.TimedOut)
                        {
                            bytesReceived = 0;
                            if(GlobalSettings.IsLeaveConnectionOpen)
                                isConnected = GetConnectionState(connection);
                            else
                                isConnected = false;
                        }
                        else
                            throw;
                    }

                    if (bytesReceived > 0)
                    {
                        messageData += Encoding.UTF8.GetString( buffer, 0, bytesReceived );
                        string ack = ProcessMessage( messageData );
                        var writeBuffer = Encoding.UTF8.GetBytes( ack );
                        if (netStream.CanWrite) { netStream.Write( writeBuffer, 0, writeBuffer.Length ); }
                        messageData = String.Empty;
                    }
                }
            }
            catch (Exception e) { ... }
            finally { FileLogger.Log( "Closing the message stream.", Verbose.Debug, DeviceName ); }
        }
    }
对于大多数客户机,代码运行正常,但也有少数客户机似乎为每条消息创建了新连接。我怀疑问题在于我如何处理IOException。对于失败的系统,代码似乎直到第一条消息传入后30秒才到达finally语句,每条消息都会创建一个新的ReceiveMessage线程。因此,日志将显示传入的消息,其中30秒将开始显示有关消息流关闭的多条消息

下面是我如何检查连接,以防这很重要

public static bool GetConnectionState( TcpClient tcpClient )
{
    var state = IPGlobalProperties.GetIPGlobalProperties()
        .GetActiveTcpConnections()
        .FirstOrDefault( x => x.LocalEndPoint.Equals( tcpClient.Client.LocalEndPoint )
        && x.RemoteEndPoint.Equals( tcpClient.Client.RemoteEndPoint ) );
    return state != null ? state.State == TcpState.Established : false;
}

你在相当多的层面上(以一种更糟糕的方式)重新发明轮子:

  • 你在做伪阻塞套接字。在像Linux这样没有真正线程的操作系统中,再加上为每个连接创建一个全新的线程,可能会很快变得昂贵。相反,您应该创建一个没有读取超时(-1)的纯阻塞套接字,并只监听它。与UDP不同,TCP将捕获客户端终止的连接,而无需轮询

  • 您之所以这样做,是因为您重新发明了标准的Keep-Alive TCP机制。它已经写好了,而且工作效率很高,只要使用它就可以了。另外,标准的保持活动机制位于客户端,而不是服务器端,因此对您的处理更少


  • 编辑:和3。你真的需要缓存你辛苦创建的线程。如果您有那么多的长期连接,每个线程只有一个套接字通信,那么系统线程池就不够了,但是您可以构建自己的可扩展线程池。您也可以使用
    select
    在一个线程上共享多个套接字,但这会大大改变您的逻辑。

    我会放弃多线程,让它点击而不是点击谢谢您的回复。我有1秒的超时时间,这样当服务停止时监听器可以安全地关闭(DeviceState==State.Running)。如果我使用纯阻塞网络流,我的理解是,在收到消息之前,我没有办法阻止它。理论上,我应该只从客户端获得一个连接,所以我应该只有一个线程。然而,情况显然并非总是如此,这就是为什么我允许多个,并试图关闭那些不再使用的。但不确定这是正确的/最好的做法。您可以关闭代码中的套接字,也可以中止线程。我建议您关闭侦听器套接字,以唤醒您的侦听线程,并出现“连接已关闭”异常。此外,如果您首先假设世界上只有一个连接,在这个世界上,您可以通过隧道从手机的另一端获取新的IP(从而连接),那么您需要阅读更多有关现代套接字使用的信息,或者使用已经创建的框架(比如asp.net mvc core和web服务),或者更好,两者都使用!