使用TCPClient接收字节流占用了太多CPU



我有一个服务,它使用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峰值时,它都会向服务发送多个字节流。我在服务器端尝试了一些方法来解决这个问题(包括在网络流上使用BeginReadEndRead(。唯一有帮助的是,当我从客户端发送较大的字节流块时,但我无法控制发送数据的第三方系统。

我曾认为接受所有套接字连接可能会起作用,但随后会限制"套接字"的数量;ReadStream";任务,但我不知道接受一个套接字然后在一段时间内不从中读取是否有任何有害影响。

不需要像内置的那样使用异步回调来连续读取流,因此它只在需要时调用,您可以执行类似的操作

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);
stream = tcpClient.GetStream(); //you need to have the stream declared at the start of the program
//set it up with th receive callback called RecieveCallback
//the 4096 is just so that any data more than that won't be read but it is highly unlickely for a peice of data above 4096 to appear
stream.BeginRead(4096, 0, 4096, ReceiveCallback, null);
Task.Run(() => ReadStream(stream, token));
}
}
//then we setup the RecieveCallback event
private void ReceiveCallback(IAsyncResult _result)
{
try
{
int _byteLength = stream.EndRead(_result); //find how much data it is
if (_byteLength <= 0)
{
//if this is true you are disconnected
return;
}
byte[] _data = new byte[_byteLength];
Array.Copy(4096, _data, _byteLength);
DoSomethingWith(_data)
stream.BeginRead(4096, 0, 4096, ReceiveCallback, null); //start listening again
}
catch
{
//disconnect
}
}

顺便说一句,运行代码片段是行不通的,这是我写代码的唯一方法,它看起来很好

我个人会尝试通过为每个连接创建一个单独的BackgroundWorker来解决这个问题,这些连接在完成时会失效。这会让你更好地利用CPU线程,让操作系统调度程序处理它

也可能是CPU的功能不够强大,无法多次处理这么多传入数据。

否则,我将使用网关来获取数据并将其发送到RabbitMQ队列,并以较低的速度处理该数据。

最新更新