【发布时间】:2021-04-17 23:17:57
【问题描述】:
我正在尝试提高基于 .NET 5 的网络代码的性能以提高吞吐量。我一直在使用标准的 .NET Sockets 实现——但是当涉及到大约 200Mbps(1kb 数据包)的吞吐量时,我遇到了障碍。我已经尝试了异步模式的两种变体(Begin... 和 SendToAsync...),并使用了使用同步操作的专用线程 - 但无法进一步提高性能。发送方和接收方的整体 CPU 负载不到 50% - 机器以 1Gbps 的速度连接。
我还尝试使用 Pcap.Net 甚至是 Windows 数据包过滤器驱动程序 (http://ntkernel.com) 来插入套接字层下方的 NDIS。
阅读表明注册 I/O 可能是一个好方法,但在我的情况下,我需要使用 SocketType.Raw(因为我需要处理 GRE、SCTP 和除 UDP 和 TCP 之外的各种其他协议)。注册 I/O API 似乎不支持原始(IP 级)套接字(请参阅https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_riocreaterequestqueue)
分析器建议将时间花在 WSASendTo 中 - 并且 .NET Socket 类几乎没有引入额外的开销。这表明即使直接使用 Winsock 从 C# 迁移到 C++ 也不太可能有太大帮助。我宁愿避开内核开发。
有什么类似于注册 I/O 的东西可以与原始套接字一起使用吗?注册 I/O 应该支持 UDP 但不直接支持 IP 似乎很奇怪。还有什么其他想法可以解决这个瓶颈吗?与原始套接字(相对于 UDP 和 TCP)相关的 Winsock 是否存在任何固有的低效率?
根据要求 - 添加了一个简单示例,以尽可能快地发送 1Kb 虚拟 GRE 数据包。这导致大约 270Mbps 的吞吐量和大约 30% 的 CPU 均匀分布在所有内核上。使用 localhost 的性能大致相同 - 我认为排除网卡是瓶颈。
using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
namespace RawSender
{
class Program
{
private static readonly ConcurrentBag<SocketAsyncEventArgs> ArgsPool = new ConcurrentBag<SocketAsyncEventArgs>();
static void Main(string[] args)
{
var Destination = IPAddress.Parse(args[0]);
// Create pool of SocketAsyncEventArgs for the async operations
for (int Index=0;Index<10_000;Index++)
{
var Args = new SocketAsyncEventArgs();
Args.SetBuffer(new byte[1000]);
Args.Completed += OnComplete;
Args.RemoteEndPoint = new IPEndPoint(Destination, 0);
ArgsPool.Add(Args);
}
// Send 1 million dummy 1kb packets using IP protocol 47 (GRE)
var Socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, (ProtocolType)47);
for(int Scan=0;Scan<1_000_000;Scan++)
{
if (!ArgsPool.TryTake(out SocketAsyncEventArgs Args)) throw new Exception("Args pool empty");
if (!Socket.SendToAsync(Args))
{
OnComplete(null, Args);
}
}
}
private static void OnComplete(object sender, SocketAsyncEventArgs e)
{
ArgsPool.Add(e);
}
}
}
【问题讨论】:
-
早在 RIO 出现之前,人们就已经在编写高性能套接字代码了。使用 RIO 只是通过减少在用户模式和内核模式之间转换的一些开销来提高效率。
-
感谢您的回复。不确定我可以提供什么示例 - 因为我在询问类似于 RIO 的支持原始套接字的 API 的可用性。但是,我添加了一个非常简单的示例,该示例演示了使用标准异步套接字的有限吞吐量。
-
TPL DataFlow 在这里有用吗?
-
我已经在平台内使用了 TPL,但除了通过标准 Sockets 实现之外,我不知道有任何方法可以发送原始数据包 - 这不能提供所需的性能。跨度>