的TcpListener的应用程序,不按比例增长不按、应用程序、比例、TcpListener

2023-09-03 05:30:59 作者:时光凉、春衫薄

我有基于一个的TCPListener 回声服务器应用程序。它接受客户端,读出的数据,并返回相同的数据。我已经开发它使用异步/等待的方法,使用该框架提供的 XXXAsync 的方法。

我已经设置的性能计数器来衡量有多少邮件和字节进出,以及有多少连接插座。

我创建了一个启动1400异步的TcpClient 测试应用程序,并发送1KB的消息每隔100-500ms。客户端具有在开始10-1000ms之间的随机等待开始,所以他们不会尝试在同一时间连接所有。我工作得很好,我可以在PerfMonitor连接1400看到,在优良率发送消息。我跑从另一台计算机的客户端应用程序。服务器的CPU和内存使用情况是非常少的,它是英特尔酷睿i7 8GB的RAM。客户似乎更忙了,它与4GB内存的酷睿i5,但还是连25%。

现在的问题是,如果我启动另一个客户端应用程序。连接在启动客户端失败。我看不出在每秒消息的大幅增加(增加更多或更少的20%),但我看到连接的客户端的数量就在1900-2100,而不是预期的2800。性能略有降低,和图形显示最大值和每秒分消息之间更大的变化比以前。

不过,CPU占用率甚至不是40%,内存使用量仍然很少。我试图增加的数量或池中的线程客户端和服务器:

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

在服务器中,连接都接受在一个循环:

 ,而(真)
{
    VAR的客户=等待_server.AcceptTcpClientAsync();
    HandleClientAsync(客户端);
}
 

HandleClientAsync 函数返回一个工作,但正如你看到的循环不会等待处理,只是继续接受其他客户端。在处理函数是这样的:

 公共异步任务HandleClientAsync(TcpClient的客户端)
{
    而(ws.Connected&安培;&安培;!_cancellation.IsCancellationRequested)
    {
        VAR味精=等待ReadMessageAsync(客户端);
        等待WriteMessageAsync(客户端,味精);
    }
}
 
快速读懂 互联网女皇 2019趋势报告 阿尔法公社推荐

这两个功能只能读取和异步写入数据流。

我看到我就可以开始了的TCPListener 指示积压量,但什么是默认值

为什么可能是原因程序不扩大,直到它达到最高的CPU?

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

更新

我已经试过了 Task.Yield Task.Run 的方法,并没有帮助。

这也发生在服务器和客户端在本地运行在同一台计算机上。递增的客户或每秒的消息数,实际上降低了服务的吞吐量。 600客户端发送消息每100毫秒,产生1000个客户端发送比报文中每个100毫秒更高的吞吐量。

在客户端上,当连接超过〜2000客户端是两个我看到的异常。随着1500年左右我看到了异常的开头,但客户最终连接。拥有超过1500我看到很多连接/断开的:

  

一个现有的连接被远程主机强行关闭   (System.Net.Sockets.SocketException)一个   System.Net.Sockets.SocketException被抓:一个现有的连接   被强行远程主机关闭

     

无法将数据写入传输连接:现有   连接被强行关闭远程主机。   (System.IO.IOException)一个System.IO.IOException被抛出:无法   数据写入传输连接:一个现有的连接。   由远程主机强行关闭。

更新2

我已经建立了一个非常与服务器和客户端简单的项目使用异步/计谋,它扩展为的预期。

在那里我有可扩展性问题,该项目是这个WebSocket的服务器,即使使用相同的做法,显然东西是导致冲突。有控制台应用程序托管的组件以及一个控制台应用程序的生成负载(尽管它要求至少是Windows 8)。

请注意,我不是要求的答案,直接解决问题,但对于技术或方法,以找出是什么原因造成了这一论点。

解决方案

我已成功地扩展到6000无左右每秒24000信息问题和处理从机连接无机(无本地主机测试)并发连接,并使用只有约80物理线程。

有一些教训,我学会了:

增加线程池的大小让事情变得更糟

除非你知道自己在做什么,不要做什么。

呼叫Task.Run或Task.Yield

收益

要确保您出席了该方法的其余部分释放调用线程。

ConfigureAwait(假)

这是你的,如果你有信心,你是不是在单个线程同步的情况下可执行应用程序,这使得任何线程拿起延续,而不是等待专门为开始成为自由的人。

字节[]

内存设置表明,应用程序是在建立字节[] 情况下,花费太多的内存和时间。所以我设计了几个策略,以重新使用现有的,或者只是工作到位,而不是创建新的及复印件。 GC的性能计数器(特别是%的时间在气相色谱法,这是55%左右),提高了报警的东西是不正确的。另外,我用 BitArray 实例检查字节位,是什么造成了一定的内存开销一样,所以我有位聪明的操作取代他们,并改善了。后来我发现不是WCF使用字节[] 池来解决这个问题。

异步并不意味着快捷

异步允许很好的规模,但它是有代价的。仅仅因为有一个可用的异步操作并不意味着你应该使用它。使用异步编程,当你presume它会得到实际的响应之前等待一段时间。如果您确定数据是否有或反应将是迅速,进行同步。

支持同步和异步繁琐

您必须实现两倍的方法,有来自同步code rehusing异步无防弹方法。

I have an ECHO server application based on a TCPListener. It accepts clients, read the data, and returns the same data. I have developed it using the async/await approach, using the XXXAsync methods provided by the framework.

I have set performance counters to measure how many messages and bytes are in and out, and how many connected sockets.

I have created a test application that starts 1400 asynchronous TCPClient, and send a 1Kb message every 100-500ms. Clients have a random waiting start between 10-1000ms at the beginning, so they not try to connect all at the same time. I works well, I can see in the PerfMonitor the 1400 connected, sending messages at good rate. I run the client app from another computer. The server's CPU and memory usage are very little, it is a Intel Core i7 with 8Gb of RAM. The client seems more busy, it is an i5 with 4Gb of RAM, but still not even the 25%.

The problem is if I start another client application. Connections start to fail in the clients. I do not see a huge increase in the messages per second (a 20% increase more or less), but I see that the number of connected clients is just around 1900-2100, rather than the 2800 expected. Performance decreases a little, and the graph shows bigger variations between max and min messages per second than before.

Still, CPU usage is not even the 40% and memory usage is still little. I have tried to increase the number or pool threads in both client and server:

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

In the server, the connections are accepted in a loop:

while(true)
{
    var client = await _server.AcceptTcpClientAsync();
    HandleClientAsync(client);
}

The HandleClientAsync function returns a Task, but as you see the loop does not wait for the handling, just continues to accept another client. That handling function is something like this:

public async Task HandleClientAsync(TcpClient client)
{    
    while(ws.Connected && !_cancellation.IsCancellationRequested)
    {
        var msg = await ReadMessageAsync(client);
        await WriteMessageAsync(client, msg);
    }
}

Those two functions only read and write the stream asynchronously.

I have seen I can start the TCPListener indicating a backlog amount, but what is the default value?

Why could be the reason why the app is not scaling up till it reaches the max CPU?

Which would be the approach and tools to find out what the actual problem is?

UPDATE

I have tried the Task.Yield and Task.Run approaches, and they didn't help.

It also happens with server and client running locally in the same computer. Incrementing the amount of clients or messages per second, actually reduces the service throughput. 600 clients sending a message each 100ms, generates more throughput than 1000 clients sending a message each 100ms.

The exceptions I see on the client when connecting more than ~2000 clients are two. With around 1500 I see the exceptions at the beginning but the clients finally connect. With more than 1500 I see lot of connection/disconnection :

"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."

UPDATE 2

I have set up a very simple project with server and client using async/await and it scales as expected.

The project where I have the scalability problem is this WebSocket server, and even when it uses the same approach, apparently something is causing contention. There is a console application hosting the component, and a console application to generate load (although it requires at least Windows 8).

Please note that I am not asking for the answer to fix the problem directly, but for the techniques or approaches to find out what is causing that contention.

解决方案

I have managed to scale up to 6,000 concurrent connections without problems and processing around 24,000 messages per second connecting from machine no machine (no localhost test) and using only around 80 physical threads.

There are some lessons I learnt:

Increasing the thread pool size made things worse

Do not do unless you know what you are doing.

Call Task.Run or yield with Task.Yield

To ensure you release the calling thread from attending the rest of the method.

ConfigureAwait(false)

From your executable application if you are confident you are not in a single threaded synchronization context, this allows any thread to pick up the continuation rather than wait specifically for the one that started to become free.

Byte[]

The memory profiler showed that the app was spending too much memory and time in creating Byte[] instances. So I designed several strategies to reuse the available ones, or just work "in place" rather than create new ones and copy. The GC performance counters (specifically "% time in GC", that was around 55%) raised the alarm that something was not right. Also, I was using BitArray instances to check bits in bytes, what caused some memory overhead as well, so I replace them with bit wise operations and it improved. Later on I discovered than WCF uses a Byte[] pool to cope with this problem.

Asynchronous does not mean fast

Asynchronous allows scale nicely, but it has a cost. Just because there is an available asynchronous operation does not mean you should use it. Use asynchronous programming when you presume it will take sometime waiting before getting the actual response. If you are sure the data is there or the response will be quick, proceed synchronously.

Support sync and async is tedious

You have to implement the methods twice, there is no bulletproof way of rehusing async from sync code.