c# - TCP 服务器连接导致处理器过载
问题描述
我有一个 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 机制。它已经编写好并且可以有效地工作,只需使用它。作为奖励,标准的 Keep-Alive 机制在客户端,而不是服务器端,因此对您的处理更少。
编辑:还有 3. 你真的需要缓存你辛苦创建的线程。如果您有那么多长期连接且每个线程只有一个套接字通信,则系统线程池将不够用,但您可以构建自己的可扩展线程池。您还可以使用 在一个线程上共享多个套接字select
,但这会大大改变您的逻辑。
推荐阅读
- javascript - 像 div 来回动画的滑块?
- video - 最佳 Final Cut Pro 导出质量选项
- c# - 在客户端计算机上未安装 SQL Server Management Studio 的情况下运行 C# 应用程序
- php - phpbrew php7 抛出 gnutls_handshake() 失败:非法参数
- stored-procedures - 在存储过程 SQL Server 2012 中使用日期时间参数
- spring - 发送请求时在 Jwt 令牌过滤器中获取空令牌?
- c++ - 如何使用 vtable 将类实例正确写入和读取到 QSharedMemory?
- ssis - 当我们在脚本中放置断点时,脚本任务运行良好,但在删除断点时进入无限循环
- c# - VssClientCredentials Interactive Popup 缩小 Windows.Forms 元素?
- javascript - 无法显示反应图片应用程序的画布元素