如何在单个TCP套接字上最大化吞吐量?

本文关键字:最大化 吞吐量 套接字 TCP 单个 | 更新日期: 2023-09-27 17:50:37

我试图让标准示例"echo"客户端/服务器应用程序尽可能快地运行,我确信网络是一个限制因素。我有一个1千兆网卡,当我使用资源监视器时,我只从客户端获得7兆比特。

我了解套接字,消息帧和长度指示器的基础知识,接收长度指示器指示的所有字节。保持数据包存活,半打开连接等

我开始使用标准套接字操作,然后切换到使用async。(我没有将send更改为async,因为有人(似乎有知识的人说它不应该有任何影响))我有完全相同的性能,我只能认为所有的材料都是假设我可以在同一线程上完成其他工作。但在我的快速测试中,我指定了1个线程在循环中持续旋转发送,另一个完全不同的线程用于接收。

我已经尝试了一切,完全失去了在哪里我可以得到更多的性能。我使用IPerf,它报告的速度为每秒1千兆比特,资源监视器也显示它正在吞噬带宽。

即使有人可以给我指出一个更完整的例子。我所遇到的大多数都是微不足道或不完整的。

下面是通用代码。

class Program
{
private static Socket sock;
private static BlockingCollection<string> queue;
private static int bytesReceived;
private static byte[] dataBuffer;
private static readonly byte[] lengthBuffer = new byte[4];
private static byte[] PrependLengthIndicator(byte[] data)
{
    return BitConverter.GetBytes(data.Length).Concat(data).ToArray();
}
private static void Receive()
{
    if (dataBuffer == null)
    {
        sock.BeginReceive(lengthBuffer, 0, 4, SocketFlags.None, ReceiveCallback, null);
    }
    else
    {
        sock.BeginReceive(dataBuffer, 0, bytesReceived, SocketFlags.None, ReceiveCallback, null);
    }
}
private static void ReceiveCallback(IAsyncResult ar)
{
    bytesReceived += sock.EndReceive(ar);
    if (dataBuffer == null)
    {
        // Currently receiving length indicator
        if (bytesReceived >= 4)
        {
            var length = BitConverter.ToInt32(lengthBuffer, 0);
            dataBuffer = new byte[length];
            bytesReceived = 0;
        }
    }
    else
    {
        if (bytesReceived == dataBuffer.Length)
        {
            // Finished reading
            var request = Encoding.ASCII.GetString(dataBuffer);
            dataBuffer = null;
            bytesReceived = 0;
            queue.Add(request);
        }
    }
    ContinueReading();
}
private static void ContinueReading()
{
    // Read into the appropriate buffer: length or data
    if (dataBuffer != null)
    {
        sock.BeginReceive(dataBuffer, bytesReceived, dataBuffer.Length - bytesReceived, SocketFlags.None, ReceiveCallback, null);
    }
    else
    {
        sock.BeginReceive(lengthBuffer, bytesReceived, lengthBuffer.Length - bytesReceived, SocketFlags.None, ReceiveCallback, null);
    }
}
}

服务器部分:

static void Main(string[] args)
{
        var listenSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        listenSock.Bind(new IPEndPoint(IPAddress.Parse(ConfigurationManager.AppSettings["LocalIp"]), 3333));
        listenSock.Listen(10);
        Console.WriteLine("Server started...");
        sock = listenSock.Accept();
        Console.WriteLine("Connection accepted.");
        queue = new BlockingCollection<string>();
        Receive();
        var count = 0;
        var sender = new Thread(() =>
            {
                while (true)
                {
                    var bar = queue.Take() + "Resp";
                    count++;
                    var resp = Encoding.ASCII.GetBytes(bar);
                    var toSend = PrependLengthIndicator(resp);
                    if (count % 10000 == 0)
                    {
                        Console.WriteLine(bar);
                    }
                    sock.Send(toSend);
                }
            });
        sender.Start();
    }

下面是客户端部分:

static void Main(string[] args)
{
    sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        Console.WriteLine("Connecting...");
        sock.Connect(IPAddress.Parse(ConfigurationManager.AppSettings["EndPointIp"]), 3333);
        Console.WriteLine("Connected.");
        Receive();
        var count = 0;
        while(true)
        {
            count++;
            var foo = "Echo-" + count;
            var data = Encoding.ASCII.GetBytes(foo);
            var toSend = PrependLengthIndicator(data);
            sock.Send(toSend);
        }
    }

如何在单个TCP套接字上最大化吞吐量?

您正在发送小消息。想想看,要使每秒1 Gbit/s的链路饱和,你需要多少个这样的节点。每个对套接字的调用都会消耗CPU。

发送更大的消息。另外,尽量不要总是分配新的缓冲区。不要使用Enumerable.Concat来连接字节缓冲区,因为这是一种非常低效的逐字节操作方式。使用Array.Copy预分配数组。

如果你使用很少的线程切换到同步IO,因为它会更快(真的!)它的开销更小)。

您可以通过运行一个愚蠢的无限发送循环来确认这个答案是正确的,该循环始终同步发送64KB的缓冲区。它会使链接饱和