我该如何降低套接字延迟?

3
我写了一个HTTP代理,它做了一些这里不相关的事情,但它增加了客户端的服务时间(没有代理为600us,有代理为60000us)。我认为我已经找到了大部分时间是从哪里来的——在我的代理完成发送回客户端之后,到客户端完成接收它。目前,服务器、代理和客户端都在同一主机上运行,使用本地地址。

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

代理监听客户端连接的套接字设置为hints AF_UNSPEC、SOCK_STREAM和AI_PASSIVE。假设从accept()中得到的套接字将具有相同的参数?

如果我正确地理解了所有这些,Apache可以在600us内完成所有操作(包括导致这40000us延迟的任何等效操作)。有人能建议可能是什么原因吗?我尝试设置TCP_NODELAY选项(我知道我不应该这样做,只是为了看看它是否有所不同),完成发送和完成接收之间的延迟就会降低,我忘记了数字,但是小于1000us。

这一切都在Ubuntu Linux 2.6.31-19上进行。感谢任何帮助。

7个回答

43

Linux系统中的TCP ACK延迟为40毫秒,这表明您可能遇到了延迟确认和Nagle算法之间的不良交互。解决此问题的最佳方法是在等待响应之前,使用单个send()sendmsg()调用发送所有数据。如果无法实现,则某些TCP套接字选项,包括接收端的TCP_QUICKACK,发送端的TCP_CORKTCP_NODELAY可以提供帮助,但如果使用不当也可能会造成损害。TCP_NODELAY仅禁用Nagle算法,并且是套接字上的一次性设置,而其他两个必须在连接的生命周期内适时设置,因此使用起来可能更加棘手。


6
这个回答是关于“如何降低套接字延迟”的最佳回答--这就是问题所在。不要提高解决原帖子的特定问题的答案的评分,这并不是该网站强大的原因。这个网站之所以伟大,是因为我们可以快速找到解决我们自己问题的解决方案。因此,如果你是一个真正的StackOverflow粉丝,这种类型的答案应该被提高评分(至少是我的看法lol)。 - Jin
1
谢谢!使用setsockopt()设置TCP_NODELAY解决了我的问题。 - Adam

5

如果客户端、代理和源服务器都在同一主机上,那么你无法对代理进行有意义的性能测量。

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

你的方法论毫无意义。在实际情况下,没有人会有600微秒的延迟到他们的源服务器。在同一主机上运行所有任务会产生争用和完全不现实的网络环境。


非常感谢您的回答。我没有想到这会有这么大的区别。我已经按照您的建议在不同的主机上运行了每个程序,现在使用代理和不使用代理的服务时间几乎没有差别了。 - Ray2k
1
“没有人的源服务器延迟达到600微秒” - 这是真的吗?正常的以太网延迟大约为0.5毫秒(使用ping测试过)。 - nh2
@nh2,对的,从那里开始,然后加上大多数服务器的延迟,我认为你会得到1毫秒以上。 - Paul Draper
3
-1:你当然可以通过本地主机学到很多关于延迟的知识,例如有一个 Nagle 算法和非常短的数据包会有 40 毫秒的延迟。 - dsign

4

介绍:

我已经为mark4o正确回答降低延迟的一般问题而赞扬他。我想将这个答案翻译成如何解决我的延迟问题,因为我认为这将是大多数人在这里寻找答案的答案。

答案:

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

免责声明:

虽然这可能不能解决OP具体的问题,但大多数来到这里的人可能会寻找这个问题的一般答案。

轶事背景:

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


3

例如,来自RedHat文档:

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

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

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

2
顺便提一下,在这种情况下,你可以既保留奈格尔算法在套接字上的启用状态,又能吃掉你的蛋糕:当你已经向套接字写入了你可能会写入一段时间的所有数据后,禁用奈格尔算法,向套接字发送0字节,然后重新启用奈格尔算法。send()将强制立即发送任何未决数据。 - Jeremy Friesner

1
在你的情况下,那40毫秒可能只是一个调度时间量子。换句话说,这就是你的系统需要多长时间才能回到其他任务上。试着在真实网络上运行,你会得到完全不同的结果。如果你有一台多核机器,在Virtualbox或其他虚拟机中使用虚拟操作系统实例会给你一个更好的想法,让你知道真正会发生什么。

谢谢,我已经按照你建议的(真实网络)做了,并且现在得到了预期的结果。 - Ray2k

1

对于TCP代理来说,在局域网侧增加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

包括谷歌关于该主题的论文,

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

同时,由谷歌公司起草的一份IETF文件。

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


0

对于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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接