在 .NET 中使用原始套接字的最快方法

问题描述

我正在尝试提高基于 .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,这似乎很奇怪。我如何解决这个瓶颈的任何其他想法? Winsock 是否存在与原始套接字(与 UDP 和 TCP 相对)相关的固有低效?

根据要求 - 添加一个简单示例以尽快发送 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);
        }
    }
}

解决方法

玩弄您的代码,似乎您受到发送的数据包开销数量的限制。使用您编写的代码,我获得了 246MB/s 的吞吐量,当我将缓冲区大小增加到 10000(即比您的大 10 倍)时,我获得了 2346MB/s(即再次大了 10 倍)。

就像任何其他类型的套接字编程一样,将您的套接字消息更好地组合在一起,并且如果您每秒发送数百万个这些消息,请不要说您不能。 >