【发布时间】:2022-01-12 22:05:03
【问题描述】:
我有一个使用TcpListener 从第三方系统接收大字节流的服务。不幸的是,我没有选择更改协议或WCF 之类的选项。
当多个系统同时发送数据时,我的服务器上的 CPU 使用率飙升至近 90%,并导致其他服务出现问题。在对我的服务进行概要分析后,看起来 CPU 正在用于将字节数组从 NetworkStream 读取到 MemoryStream 中。这是我的服务器代码示例
public void StartListening(CancellationToken token) {
var listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Any, port);
listener.Start();
while (!token.IsCancellationRequested) {
var socket = listener.AcceptSocket();
TcpClient tcpClient = new TcpClient(socket);
var stream = tcpClient.GetStream();
Task.Run(()=> ReadStream(stream, token));
}
}
private void ReadStream(NetworkStream stream, CancellationToken token){
int offset = 0;
int size = EXPECTED_FILE_SIZE;
var inStream = new MemoryStream(size);
while (size > 0 && !token.IsCancellationRequested) {
try {
int readin = stream.Read(inStream.GetBuffer(), offset, size);
size -= readin;
offset += readin;
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
return;
}
}
//Do something with the memory stream
}
我编写了一个测试客户端,它会在每次 CPU 达到峰值时一次向服务发送多个字节流。我已经尝试了一些服务器端的方法来解决这个问题(包括在网络流上使用BeginRead 和EndRead)。唯一似乎有帮助的是当我从客户端发送更大的字节流块时,但我无法控制发送数据的第 3 方系统。
我认为接受所有套接字连接可能会起作用,但会限制“ReadStream”任务的数量,但我不知道接受套接字然后不从中读取是否有任何不利影响一会儿。
【问题讨论】:
-
不确定实际问题,因为代码本身看起来正确,我唯一担心的是如果
EXPECTED_FILE_SIZE大于实际传输的值怎么办?您的代码将陷入无限循环。 -
您正在使用取消令牌,但同步读取。问题:为什么要使用同步读取? Socket IO 几乎是异步读取的完美候选者。另外,你是这里的客户端还是服务器?如果您是服务器:Kestrel 和 Pipelines 是为这种情况构建的,这使得编写大规模并发服务器变得轻而易举。 Pipelines 还旨在为您处理后台缓冲区等,因此您最终不会将所有内容复制到 MemoryStream。我有一个包含 4 部分的博客,在这里:blog.marcgravell.com/2018/07/pipe-dreams-part-1.html
-
如果您是服务器,那么:使用每连接线程回答“为什么我会导致 CPU 出血?”的问题。异步是编写可扩展服务器的关键。特别是,您当前的代码正在占用线程池,这是一个糟糕的主意,原因有很多
-
这是服务器端代码。感谢您提供有关 Kestrel 的提示,这项服务越来越希望被重写。至于同步,我没有编写原始代码,但我确实尝试将 Read 调用转换为它们的 BeginRead 和 EndRead 等效项(使用 FromAsync 而不是 Task.Run),但并没有太大改善
-
您到处都缺少
asyncawait,所以这不足为奇。循环中的Task.Run不会异步生成。
标签: c# tcplistener