3

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

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

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

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

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

4

7 回答 7

43

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

于 2010-02-12T17:10:27.127 回答
5

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

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

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

于 2010-02-12T11:52:08.547 回答
4

介绍:

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

回答:

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

免责声明:

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

轶事背景故事:

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

于 2012-01-15T06:24:21.500 回答
3

例如来自 RedHat 文档:

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

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

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

于 2010-02-12T11:49:39.347 回答
1

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

于 2010-02-12T12:03:12.827 回答
1

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

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

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

还有一份谷歌的 IETF 草案,

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

于 2010-11-30T08:44:30.020 回答
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);
于 2015-06-14T21:04:30.813 回答