【问题标题】:(How) Can I reduce socket latency?(如何)我可以减少套接字延迟吗?
【发布时间】:2011-01-16 03:05:39
【问题描述】:

我编写了一个 HTTP 代理,它执行一些与这里无关的事情,但它大大增加了客户端的服务时间(没有代理的 600us 与有代理的 60000us)。我想我已经找到了大部分时间的来源——在我的代理完成发送回客户端和客户端完成接收之间。目前,服务器、代理和客户端运行在同一主机上,使用 localhost 作为地址。

一旦代理完成发送(至少从 send() 返回),我打印 gettimeofday 的结果,它给出了一个绝对时间。当我的客户收到后,它会打印 gettimeofday 的结果。由于它们都在同一主机上,因此这应该是准确的。所有 send() 调用都没有标志,因此它们是阻塞的。两者相差约40000us。

它侦听客户端连接的代理套接字设置有提示 AF_UNSPEC、SOCK_STREAM 和 AI_PASSIVE。大概来自 accept() 的套接字将具有相同的参数?

如果我对这一切的理解正确,Apache 可以在 600us 内完成所有工作(包括导致 40000us 延迟的任何东西)。任何人都可以提出可能导致这种情况的原因吗?我已经尝试设置 TCP_NODELAY 选项(我知道我不应该这样做,这只是为了看看它是否有所不同)并且完成发送和完成接收之间的延迟下降了,我忘记了数字但

这一切都在 Ubuntu Linux 2.6.31-19 上。感谢您的帮助

【问题讨论】:

    标签: c linux sockets client-server latency


    【解决方案1】:

    来自例如RedHat 文档:

    对于每个发送的数据包都需要较低延迟的应用程序应该在启用了 TCP_NODELAY 的套接字上运行。它可以通过带有套接字 API 的 setsockopt 命令启用:

    int one = 1;
    setsockopt(descriptor, SOL_TCP, TCP_NODELAY, &one, sizeof(one));
    

    为了有效地使用它,应用程序必须避免执行小的、逻辑相关的缓冲区写入。因为启用了 TCP_NODELAY,所以这些小的写入会使 TCP 将这些多个缓冲区作为单独的数据包发送,这会导致整体性能不佳。

    【讨论】:

    • 顺便说一句,在这种情况下也可以吃掉你的蛋糕:你可以在你的套接字上保持 Nagle 的启用,然后当你将所有数据写入套接字时您可能会写一段时间,禁用 Nagle,在套接字上发送() 0 个字节,然后再次重新启用 Nagle。 send() 将强制立即发送任何待处理的数据。
    【解决方案2】:

    您无法真正对同一主机上的客户端、代理和源服务器的代理进行有意义的性能测量。

    将它们全部放在网络上的不同主机上。全部使用真正的硬件机器,或专门的硬件测试系统(例如 Spirent)。

    您的方法毫无意义。无论如何,实际上没有人对他们的原始服务器有 600us 的延迟。在同一台主机上运行所有任务会产生争用和完全不切实际的网络环境。

    【讨论】:

    • 非常感谢您的回答。我没想到会有这么大的不同。我已经按照您的建议做了,并在不同的主机上运行了每一个,现在服务时间与没有代理的差异可以忽略不计。
    • “没有人对他们的原始服务器有 600us 的延迟”——这是真的吗?普通以太网有大约 0.5 毫秒的延迟(尝试使用 ping)。
    • @nh2,对,所以从那里开始,然后添加大多数服务器的延迟,我认为你已经得到了 1ms+。
    • -1:您当然可以仅使用 localhost 了解很多关于延迟的知识,例如 Nagle 算法和非常短的数据包的 40 毫秒延迟。
    【解决方案3】:

    在您的情况下,这 40 毫秒可能只是调度程序的时间量。换句话说,这就是您的系统需要多长时间才能回到其他任务。在真实的网络上试一试,你会得到完全不同的画面。如果您有一台多核机器,在 Virtualbox 或其他一些 VM 中使用虚拟操作系统实例可以让您更好地了解实际情况。

    【讨论】:

    • 谢谢,我已经按照你的建议做了(真实网络),现在得到了预期的结果。
    【解决方案4】:

    40ms 是 Linux 上的 TCP ACK 延迟,这表明您可能遇到延迟的 ack 和Nagle algorithm 之间的不良交互。解决此问题的最佳方法是在等待响应之前,通过一次调用send()sendmsg() 发送所有数据。如果这是不可能的,那么某些TCP socket options 包括TCP_QUICKACK(接收方)、TCP_CORK(发送方)和TCP_NODELAY(发送方)可以提供帮助,但如果使用不当也会造成伤害。 TCP_NODELAY 只是禁用 Nagle 算法,并且是对套接字的一次性设置,而其他两个必须在连接生命周期中的适当时间设置,因此使用起来可能会比较棘手。

    【讨论】:

    • 这个答案是“如何减少套接字延迟?”这个问题的最佳答案。 ——这就是问题所在。不要升级解决 OP 特定问题的答案,这不是让这个网站如此强大的原因。这个网站很棒,因为我们可以快速找到我们的问题的解决方案。因此,如果您是真正的 StackOverflow'er,那么这种答案就是您评价的答案(至少恕我直言,哈哈)。
    • 谢谢!使用 setsockopt() 设置 TCP_NODELAY 解决了我的问题。
    【解决方案5】:

    对于 TCP 代理,在 LAN 端增加 TCP 初始窗口大小似乎是谨慎的做法,如 linux-netdev 和 / 中所讨论的。最近。

    http://www.amailbox.org/mailarchive/linux-netdev/2010/5/26/6278007

    http://developers.slashdot.org/story/10/11/26/1729218/Google-Microsoft-Cheat-On-Slow-Start-mdash-Should-You

    包括 Google 关于该主题的论文,

    http://www.google.com/research/pubs/pub36640.html

    还有一份 Google 的 IETF 草案,

    http://zinfandel.levkowetz.com/html/draft-ietf-tcpm-initcwnd-00

    【讨论】:

      【解决方案6】:

      简介:

      我已经赞扬了 mark4o 对降低延迟的一般问题的真正正确答案。我想根据它如何帮助解决我的延迟问题来翻译答案,因为我认为这将是大多数人来这里寻找的答案。

      回答:

      在实时网络应用(例如多人游戏)中,尽快在节点之间获取短消息至关重要,请关闭 NAGLE。在大多数情况下,这意味着将“无延迟”标志设置为 true。

      免责声明:

      虽然这可能无法解决特定于 OP 的问题,但大多数来到这里的人可能会寻找有关延迟问题的一般问题的答案。

      轶事背景:

      在我添加代码以分别发送两条消息之前,我的游戏运行良好,但它们在执行时间上非常接近。突然间,我得到了 250 毫秒的额外延迟。由于这是更大的代码更改的一部分,我花了两天时间试图弄清楚我的问题是什么。当我将这两条消息合二为一时,问题就消失了。逻辑引导我到 mark4o 的帖子,所以我将 .Net 套接字成员“NoDelay”设置为 true,我可以连续发送任意数量的消息。

      【讨论】:

        【解决方案7】:

        对于 Windows,我不确定设置 TCP_NODELAY 是否有帮助。我试过了,但延迟仍然很糟糕。有人建议我尝试 UDP,然后就成功了。

        一些复杂的 UDP 示例对我不起作用,但我遇到了一个简单的示例,它成功了...

        #include <Winsock2.h>
        #include <WS2tcpip.h>
        #include <system_error>
        #include <string>
        #include <iostream>
        
        class WSASession
        {
        public:
            WSASession()
            {
                int ret = WSAStartup(MAKEWORD(2, 2), &data);
                if (ret != 0)
                    throw std::system_error(WSAGetLastError(), std::system_category(), "WSAStartup Failed");
            }
            ~WSASession()
            {
                WSACleanup();
            }
        private:
            WSAData data;
        };
        
        class UDPSocket
        {
        public:
            UDPSocket()
            {
                sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
                if (sock == INVALID_SOCKET)
                    throw std::system_error(WSAGetLastError(), std::system_category(), "Error opening socket");
            }
            ~UDPSocket()
            {
                closesocket(sock);
            }
        
            void SendTo(const std::string& address, unsigned short port, const char* buffer, int len, int flags = 0)
            {
                sockaddr_in add;
                add.sin_family = AF_INET;
                add.sin_addr.s_addr = inet_addr(address.c_str());
                add.sin_port = htons(port);
                int ret = sendto(sock, buffer, len, flags, reinterpret_cast<SOCKADDR *>(&add), sizeof(add));
                if (ret < 0)
                    throw std::system_error(WSAGetLastError(), std::system_category(), "sendto failed");
            }
            void SendTo(sockaddr_in& address, const char* buffer, int len, int flags = 0)
            {
                int ret = sendto(sock, buffer, len, flags, reinterpret_cast<SOCKADDR *>(&address), sizeof(address));
                if (ret < 0)
                    throw std::system_error(WSAGetLastError(), std::system_category(), "sendto failed");
            }
            sockaddr_in RecvFrom(char* buffer, int len, int flags = 0)
            {
                sockaddr_in from;
                int size = sizeof(from);
                int ret = recvfrom(sock, buffer, len, flags, reinterpret_cast<SOCKADDR *>(&from), &size);
                if (ret < 0)
                    throw std::system_error(WSAGetLastError(), std::system_category(), "recvfrom failed");
        
                // make the buffer zero terminated
                buffer[ret] = 0;
                return from;
            }
            void Bind(unsigned short port)
            {
                sockaddr_in add;
                add.sin_family = AF_INET;
                add.sin_addr.s_addr = htonl(INADDR_ANY);
                add.sin_port = htons(port);
        
                int ret = bind(sock, reinterpret_cast<SOCKADDR *>(&add), sizeof(add));
                if (ret < 0)
                    throw std::system_error(WSAGetLastError(), std::system_category(), "Bind failed");
            }
        
        private:
            SOCKET sock;
        };
        

        服务器

        #define TRANSACTION_SIZE    8
        
        static void startService(int portNumber)
        {
            try
            {
                WSASession Session;
                UDPSocket Socket;
                char tmpBuffer[TRANSACTION_SIZE];
                INPUT input;
                input.type = INPUT_MOUSE;
                input.mi.mouseData=0;
                input.mi.dwFlags = MOUSEEVENTF_MOVE;
        
                Socket.Bind(portNumber);
                while (1)
                {
                    sockaddr_in add = Socket.RecvFrom(tmpBuffer, sizeof(tmpBuffer));
        
                    ...do something with tmpBuffer...
        
                    Socket.SendTo(add, data, len);
                }
            }
            catch (std::system_error& e)
            {
                std::cout << e.what();
            }
        

        客户

        char *targetIP = "192.168.1.xxx";
        Socket.SendTo(targetIP, targetPort, data, len);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-04-16
          • 1970-01-01
          • 2016-11-19
          • 2016-04-19
          • 2012-08-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多