.net - 基于 TcpListener 的应用程序不能很好地扩展

标签 .net multithreading asynchronous async-await tcplistener

我有一个基于 TCPListener 的 ECHO 服务器应用程序.它接受客户端,读取数据,并返回相同的数据。我使用 async/await 方法开发了它,使用 XXXAsync框架提供的方法。

我已经设置了性能计数器来测量有多少消息和字节进出,以及有多少连接的套接字。

我创建了一个启动 1400 异步的测试应用程序 TCPClient , 每 100-500ms 发送一个 1Kb 的消息。客户端在开始时会随机等待 10-1000 毫秒,因此他们不会尝试同时连接所有内容。我工作得很好,我可以在 PerfMonitor 中看到 1400 已连接,以良好的速率发送消息。我从另一台计算机运行客户端应用程序。服务器的 CPU 和内存使用量很少,它是带有 8Gb RAM 的 Intel Core i7。客户端似乎更忙,它是带有 4Gb RAM 的 i5,但仍然没有达到 25%。

问题是如果我启动另一个客户端应用程序。客户端的连接开始失败。我没有看到每秒消息的巨大增加(或多或少增加了 20%),但我看到连接的客户端数量仅在 1900-2100 左右,而不是预期的 2800。性能略有下降,图表显示每秒最大和最小消息之间的差异比以前更大。

尽管如此,CPU 使用率甚至还不到 40%,内存使用率仍然很少。我试图增加客户端和服务器中的线程数或池线程:

ThreadPool.SetMaxThreads(5000, 5000);
ThreadPool.SetMinThreads(2000, 2000);

在服务器中,连接被循环接受:
while(true)
{
    var client = await _server.AcceptTcpClientAsync();
    HandleClientAsync(client);
}
HandleClientAsync函数返回 Task ,但正如您看到的,循环不等待处理,只是继续接受另一个客户端。那个处理函数是这样的:
public async Task HandleClientAsync(TcpClient client)
{    
    while(ws.Connected && !_cancellation.IsCancellationRequested)
    {
        var msg = await ReadMessageAsync(client);
        await WriteMessageAsync(client, msg);
    }
}

这两个函数仅异步读取和写入流。

我看到我可以启动TCPListener表示 backlog金额,但默认值是多少?

为什么应用程序在达到最大 CPU 之前不会扩展的原因?

找出实际问题的方法和工具是什么?

更新

我试过 Task.YieldTask.Run方法,他们没有帮助。

在同一台计算机上本地运行的服务器和客户端也会发生这种情况。每秒增加客户端或消息的数量,实际上会降低服务吞吐量。 600 个客户端每 100 毫秒发送一条消息,比 1000 个客户端每 100 毫秒发送一条消息产生更多的吞吐量。

当连接超过 2000 个客户端时,我在客户端上看到的异常(exception)是两个。在大约 1500 的情况下,我在开始时看到了异常,但客户端终于连接上了。超过 1500 我看到很多连接/断开连接:

"An existing connection was forcibly closed by the remote host" (System.Net.Sockets.SocketException) A System.Net.Sockets.SocketException was caught: "An existing connection was forcibly closed by the remote host"

"Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host." (System.IO.IOException) A System.IO.IOException was thrown: "Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host."



更新 2

我已经设置了很simple project with server and client using async/await它按预期扩展。

我遇到可扩展性问题的项目是 this WebSocket server ,甚至当它使用相同的方法时,显然有些东西会引起争用。有一个console application hosting the component ,以及一个控制台应用程序到 generate load (尽管它至少需要 Windows 8)。

请注意,我不是要求直接解决问题的答案,而是要找出导致该争用的原因的技术或方法。

最佳答案

我已经成功地扩展到 6,000 个并发连接而没有问题,并且每秒处理大约 24,000 条消息,从没有机器的机器(没有本地主机测试)连接并且只使用大约 80 个物理线程。
我学到了一些教训:
增加线程池大小让事情变得更糟
除非你知道自己在做什么,否则不要做。
使用 Task.Yield 调用 Task.Run 或 yield
确保您释放调用线程参与方法的其余部分。
配置等待(假)
如果您确信自己不在单线程同步上下文中,那么从您的可执行应用程序中,这允许任何线程获取延续,而不是专门等待开始变得空闲的线程。
字节[]
内存分析器显示应用程序在创建 Byte[] 时花费了太多内存和时间。实例。所以我设计了几种策略来重用可用的策略,或者只是“就地”工作而不是创建新的并复制。 GC 性能计数器(特别是“% time in GC”,大约为 55%)发出警报,指出某些事情不正确。另外,我正在使用 BitArray以字节为单位检查位的实例,这也导致了一些内存开销,所以我用按位操作替换它们并改进了。后来我发现 WCF 使用了 Byte[] pool 来解决这个问题。
异步并不意味着 fast异步允许很好地扩展,但它有成本。仅仅因为有一个可用的异步操作并不意味着您应该使用它。当您认为在获得实际响应之前需要等待一段时间时,请使用异步编程。如果您确定数据在那里或响应很快,请同步进行。
支持同步和异步很乏味
您必须两次实现这些方法,没有从同步代码中重新使用异步的防弹方法。

关于.net - 基于 TcpListener 的应用程序不能很好地扩展,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22013072/

相关文章:

.net - 解决 x64 com 互操作编码(marshal)处理问题

c# - 在 ResourceDictionary 中添加 .cs?

.net - 如何在 Excel 宏中使用 JavaScript?

wcf - Silverlight 应用程序能否从一个调用接收多个回调?

python - pygtk:打印大型输出数据集时使用 io_add_watch block 实现的异步输出

c# - 如何检测 .Net 中的装箱原因?

c++ - 从类中运行线程

java - Android java加载 fragment 无延迟

java - 线程中的某些代码不重复

c# - OrderBy 任务函数异步中的列表