(Как) я могу уменьшить задержку сокета? - PullRequest
2 голосов
/ 12 февраля 2010

Я написал 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. Спасибо за любую помощь

Ответы [ 7 ]

37 голосов
/ 12 февраля 2010

40 мс - это задержка TCP ACK в Linux, которая указывает на то, что вы, вероятно, столкнулись с плохим взаимодействием между отложенными подтверждениями и алгоритмом Nagle . Лучший способ решить эту проблему - отправить все ваши данные с помощью одного звонка на send() или sendmsg(), прежде чем ждать ответа. Если это невозможно, то некоторые опции сокета TCP , включая TCP_QUICKACK (на принимающей стороне), TCP_CORK (отправляющая сторона) и TCP_NODELAY (отправляющая сторона), могут помочь, но могут нанести вред если используется не по назначению. TCP_NODELAY просто отключает алгоритм Nagle и является одноразовой настройкой для сокета, тогда как другие два должны быть установлены в подходящее время в течение срока службы соединения и поэтому могут быть более хитрыми в использовании.

5 голосов
/ 12 февраля 2010

Вы не можете реально провести значимые измерения производительности на прокси-сервере с клиентом, прокси-сервером и исходным сервером на одном хосте.

Разместите их все на разных хостах в сети. Используйте настоящие аппаратные машины для них всех или специализированные системы тестирования оборудования (например, Spirent).

Ваша методология не имеет смысла. Ни у кого на практике нет задержек в 600 мс к серверу-источнику. Выполнение всех задач на одном и том же хосте создает конфликт и совершенно нереалистичную сетевую среду.

4 голосов
/ 15 января 2012

ВВЕДЕНИЕ:

Я уже похвалил mark4o за действительно правильный ответ на общий вопрос о снижении задержки. Я хотел бы перевести ответ с точки зрения того, как он помог решить мою проблему с задержкой, потому что я думаю, что это будет ответом, который ищет большинство людей.

ОТВЕТ:

В сетевом приложении реального времени (например, в многопользовательской игре), в котором критически важно получать короткие сообщения между узлами как можно быстрее, ВЫКЛЮЧИТЕ NAGLE. В большинстве случаев это означает установку флага «без задержки» на true.

ПРЕДУПРЕЖДЕНИЕ:

Хотя это может не решить конкретную проблему OP, большинство людей, которые приходят сюда, вероятно, будут искать этот ответ на общий вопрос о своих проблемах с задержкой.

ОБРАТНАЯ СВЯЗЬ:

Моя игра работала хорошо, пока я не добавил код для отправки двух сообщений по отдельности, но они были очень близки друг к другу во время выполнения. Внезапно я получил дополнительную задержку 250 мс. Поскольку это было частью большого изменения кода, я потратил два дня, пытаясь выяснить, в чем заключалась моя проблема. Когда я объединил два сообщения в одно, проблема исчезла. Логика привела меня к сообщению mark4o, и я установил для элемента сокета .Net «NoDelay» значение true, и я могу отправлять столько сообщений подряд, сколько захочу.

3 голосов
/ 12 февраля 2010

Например, Документация RedHat:

Приложения, которым требуется меньшая задержка для каждого отправляемого пакета, должны запускаться на сокетах с включенным TCP_NODELAY. Его можно включить с помощью команды setsockopt с помощью API сокетов:

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

Чтобы это эффективно использовалось, приложения должны избегать небольших, логически связанных операций записи в буфер. Поскольку TCP_NODELAY включен, эти небольшие записи заставят TCP отправлять эти несколько буферов в виде отдельных пакетов, что может привести к снижению общей производительности.

1 голос
/ 30 ноября 2010

Для 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

И черновик IETF также от Google,

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

1 голос
/ 12 февраля 2010

В вашем случае эти 40 мс - это, вероятно, просто квант времени планировщика. Другими словами, вот как долго ваша система возвращается к другим задачам. Попробуйте в реальной сети, вы получите совершенно другую картину. Если у вас многоядерный компьютер, использование экземпляров виртуальных ОС в Virtualbox или другой виртуальной машине даст вам гораздо лучшее представление о том, что на самом деле произойдет.

0 голосов
/ 15 июня 2015

Для 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...