【问题标题】:.NET Client - Server application gets frozen, need ideas to fix!.NET 客户端 - 服务器应用程序被冻结,需要解决的想法!
【发布时间】:2011-02-28 14:10:59
【问题描述】:

你好 Stack Overflow 的好人,
我有一个运行数百个客户端的 .NET 客户端-服务器应用程序。大约一年前,该项目从 VB6 迁移到 .NET,它是一个纸牌/棋盘游戏平台。 虽然我会尽量在下面提供尽可能多的细节,但问题是当里面有 40-70 名玩家时,频道会冻结。

架构

1.服务器 (.NET 4.0)

  • 分为三个项目:ServerNET、Listener、Channel
  • 监听器就像一个登录服务器,客户端首先连接。它负责检查版本和帐户信息等内容。 还允许客户端选择要连接的通道。它基本上是一个临时的 TCPListener,监听任何试图永远连接的人。这不是双方都被冻结的原因。
  • Channel 表示单个端口,客户端在使用 Listener 后连接到 Channels。就像航天飞机一样,这是主要部分。类似于 MIRC 频道,它将所有用户绑定在里面,大部分数据都发送给同一频道内的人,例如聊天和您可以加入的其他玩家创建的游戏,由服务器托管。这是一个控制台应用程序,用作玩家的中心。播放器信息保存在“客户端”类中,其中包括 TCPClient 和一些其他属性。每个客户端运行一个线程并进行由服务器处理的异步调用。这些“客户端”对象也保存在一个名为“客户端集合”的集合类中。当里面有大约 40-70 名玩家时,频道会冻结。每个频道最多允许 100 名玩家。
  • ServerNET 是主体,执行与整个系统相关的所有其他一般性工作,而不是特定于通道。这是一个表单应用程序,运行诸如服务器选项之类的东西。

2。客户端 (.NET 2.0)

  • 使用 TCPClient 运行,主要是单线程,而服务器是多线程。
  • 必须使用 .NET 2.0。
  • 主要由视觉效果和其他不重要的东西组成。

当有 40 多个客户端连接到单个通道时,它开始完全随机冻结(或者这就是我们现在所拥有的,没有证据或足够的数据来指出完全错误的地方)。我们真的不认为网络流量是问题(还不太确定),因为我们已经在具有各种设置的不同服务器机器上进行了尝试。我们使用的所有服务器机器都能够在硬件方面处理这么多的进程。所以这是关于方法和代码方面的事情。

我们努力解决这个问题的原因是我们不确定是什么原因造成的。请查看以下示例:
系统 A 在其频道 #1 中有 55 人在线,并且无论如何都不会被冻结。系统 A 使用 A1 IP,通道在 16xxx 端口。
系统 B 在他们的第 4 频道中有 25 人在线,它会随机冻结一两分钟。系统 B 使用 B1 IP 和 18xxx 作为通道端口。它与系统 A 位于同一台机器上,系统 A 不会被冻结。

作为结论,它看起来与在线人数无关,但当人数增加时,它会更频繁地发生。

我们尝试在 Channel 项目的无限循环中滚动 Application.DoEvents(),认为某些 X 进程导致通道进入冻结状态几分钟,从而导致通道暂停。然后它会在几秒钟内执行冻结时排队的所有操作。每个通道的 CPU 使用率平均在 7%-20% 之间,看起来越来越好。然而,这并不是永久有效的解决方案。

我们怀疑的事情:

  • 包含播放器和 TCPClients 的 ClientCollection 是从 CollectionBase 继承的。也许这会在同步过程中造成一些混乱。这在过去曾经是一个数组,我们遇到的这些问题更少。也许它不应该从 CollectionBase 继承,而是别的什么?
  • 我们正在使用 SyncLock(C# 中的锁定)来同步 ClientCollection。 (虽然我们在开始使用锁之前就遇到过这个问题)

服务器信息
英特尔至强 X3460 2.80GHz
16 GB 内存
64 位 Windows Server 2008 企业版

我知道不查看整个代码就无法解决问题,但我很遗憾无法发布代码。相反,我正在寻找一个想法来引导我走向某个方向。不过,我们很乐意分享任何其他信息来解决此问题。

感谢大家的帮助!

【问题讨论】:

  • 听起来像是某种没有代码就无法解决的竞争条件。

标签: .net client


【解决方案1】:

我们在一个非常相似的应用程序上遇到了一个非常相似的问题(我们的应用程序向大约 1300 个用户推送了统计数据)。

我最好的猜测是,在您的 TCPClient 上,您设置了无限超时。不幸的是,这是默认行为。因此,当 TCPClient 在读取时阻塞时,它有时会完全冻结。

将超时设置为 30 秒(或更适合您的情况)。

TcpClient newClient = incoming.AcceptTcpClient();
newClient.NoDelay = true;  // Send & receive immediately, even when the buffers aren't full
newClient.ReceiveTimeout = 30000;
newClient.SendTimeout = 30000;

【讨论】:

  • 将使用这些道具进行测试并让您知道(希望今天)。谢谢你的建议。
  • 我们发布了一个测试,其中 NoDelay 设置为 true,Timeouts 设置合适(如 15-20 秒)。还想更正 Timeout 道具的设置以毫秒为单位,而不是秒。测试现在进行了大约 5 个小时,还没有被冻结。但是,需要等待更多时间并让更多用户连接(使用 45ish 进行测试,它曾经被冻结这么多,这是一个好兆头)。
  • 我们已经测试了几天,没有关于它被冻结的报告。虽然仍然不确定您的解决方案是否有意义,但我仍然无法理解它是如何仅通过设置三个道具变得更好的:) 另外我想询问您对 TCPListener 和 TCPClient 对象的此类优化的进一步意见,或任何其他。我们设法在一个频道中达到了 70ish,结果完全令人满意。谢谢! (ps:当我获得 15+ 代表时,我会投票支持这个新帐户:p)
【解决方案2】:

系统冻结时是否可以进行完整的进程挂起转储? 然后您可以查看每个线程在做什么,以更好地理解原因。

  • 要进行挂起转储,您需要下载 Windwos Deugging Tools,附带 .net 4.0 SDK
  • 并使用 -Hang 标志和进程 ID 运行 AdPlus.vbs。
  • 在winDbg中运行~*e !clrstack命令获取所有调用栈

【讨论】:

  • 正在下载,将发布结果。
  • 因为这是一个挂起的情况,间隔几秒钟一个接一个地进行 2 ,3 次转储,可以让我们更好地了解哪些线程仍然在同一个地方。
  • 我得到了挂起转储文件,甚至设法用 VS 运行它。我能够检查线程,但其他一切都在 Assembly 上运行,没有任何调用堆栈。然后我运行 winDbg,点击 File->Open->Crash Dump with ~*e !clrstack 命令,我得到以下信息:“0:000> ~*e !clrstack No export clrstack found”
  • 抱歉,您需要先加载 SOS 扩展,方法如下:.loadby sos mscorwks(注意 loadby 命令前的点)
【解决方案3】:

对我来说,在服务器应用程序中使用同步套接字是一个很大的禁忌。不要为每个连接的客户端使用一个线程。不要使用TcpClient.Read/TcpClient.Send

了解BeginRead/EndRead + BeginSend/EndSend 方法。它们的扩展性比使用线程和同步方法要好得多。

更新

异步读取并不意味着您不能同步处理读取命令。读取 async 的原因是能够获得完整的命令,而不必为每个客户端使用自己的线程。

做这样的事情来阅读:

  1. 开始阅读
  2. 在 OnRead(BeginRead 回调)中。调用 EndRead
  3. 0 字节 = 断开连接
  4. 将接收到的数据附加到缓冲区(如果您的命令是字符串,请不要使用字符串作为缓冲区,请使用 StringBuilder)
  5. 检查缓冲区是否包含完整的包。
  6. 调用处理完整包的方法/事件/委托
  7. 调用 BeginRead

如您所见,处理仍然可以是同步的,您不必为每个客户端创建一个新线程。 AFAIK,.Net 使用 IO Completion 端口进行其套接字 IO 操作,可扩展性非常好。

从套接字开始时,实际上并不需要使用 BeginSend/EndSend,因为您通常只是在发送时触发并忘记。真正影响性能的是每个客户端的读取线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-24
    • 1970-01-01
    • 1970-01-01
    • 2013-11-08
    • 1970-01-01
    • 1970-01-01
    • 2015-08-06
    • 1970-01-01
    相关资源
    最近更新 更多