Расчет скорости загрузки сокета - PullRequest
5 голосов
/ 30 июня 2010

Мне интересно, кто-нибудь знает, как рассчитать скорость загрузки сокета Беркли в C ++. Мой отправка вызова не блокируется, и для отправки 5 мегабайт данных требуется 0,001 секунды, но требуется некоторое время для recv ответа (так что я знаю, что он загружается).

Это сокет TCP для HTTP-сервера, и мне нужно асинхронно проверить, сколько байтов данных было загружено / осталось. Однако я не могу найти никаких функций API для этого в Winsock, поэтому я в тупике.

Любая помощь будет принята с благодарностью.

РЕДАКТИРОВАТЬ: Я нашел решение и буду публиковать как ответ как можно скорее!

РЕДАКТИРОВАТЬ 2: Правильное решение добавлено как ответ, будет добавлено как решение через 4 часа.

Ответы [ 4 ]

5 голосов
/ 02 июля 2010

Я решил свою проблему благодаря bdolan , предложившему уменьшить SO_SNDBUF.Однако для использования этого кода вы должны заметить, что ваш код использует Winsock 2 (для перекрывающихся сокетов и WSASend).В дополнение к этому ваш SOCKET дескриптор должен быть создан аналогично:

SOCKET sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

Обратите внимание на флаг WSA_FLAG_OVERLAPPED в качестве конечного параметра.

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

Мой поток кода

Глобальные переменные

Ваш кодовый документ должен иметь следующие глобальные переменные:

#define UPLOAD_CHUNK_SIZE 4096

int g_nUploadChunks = 0;
int g_nChunksCompleted = 0;
WSAOVERLAPPED *g_pSendOverlapped = NULL;
int g_nBytesSent = 0;
float g_flLastUploadTimeReset = 0.0f;

Примечание: в моих тестах, уменьшение UPLOAD_CHUNK_SIZE приводит к повышению точности скорости загрузки, но снижает общую скорость загрузки.Увеличение UPLOAD_CHUNK_SIZE приводит к снижению точности скорости загрузки, но увеличивает общую скорость загрузки.4 килобайта (4096 байт) - хороший компромисс для файла размером ~ 500 КБ.

Функция обратного вызова

Эта функция увеличивает количество переданных байтов и завершает чанки завершенными переменными (вызываемыми после того, как чанк был полностью заполнензагружено на сервер)

void CALLBACK SendCompletionCallback(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
    g_nChunksCompleted++;
    g_nBytesSent += cbTransferred;
}

Подготовить сокет

Первоначально сокет должен быть подготовлен путем уменьшения значения SO_SNDBUF до 0.

Примечание: В моих тестах любое значение больше 0 приведет к нежелательному поведению.

int nSndBuf = 0;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&nSndBuf, sizeof(nSndBuf));

Создать WSAOVERLAPPED массив

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

// Calculate the amount of upload chunks we will have to create.
// nDataBytes is the size of data you wish to upload
g_nUploadChunks = ceil(nDataBytes / float(UPLOAD_CHUNK_SIZE));

// Overlapped array, should be delete'd after all uploads have completed
g_pSendOverlapped = new WSAOVERLAPPED[g_nUploadChunks];
memset(g_pSendOverlapped, 0, sizeof(WSAOVERLAPPED) * g_nUploadChunks);

Загрузить данные

Все данные, которые необходимо отправить, например, для целей, хранятся в переменной с именем pszData.Затем, используя WSASend, данные отправляются в блоках, определяемых константой, UPLOAD_CHUNK_SIZE.

WSABUF dataBuf;
DWORD dwBytesSent = 0;
int err;
int i, j;

for(i = 0, j = 0; i < nDataBytes; i += UPLOAD_CHUNK_SIZE, j++)
{
    int nTransferBytes = min(nDataBytes - i, UPLOAD_CHUNK_SIZE);

    dataBuf.buf = &pszData[i];
    dataBuf.len = nTransferBytes;

    // Now upload the data
    int rc = WSASend(sock, &dataBuf, 1, &dwBytesSent, 0, &g_pSendOverlapped[j], SendCompletionCallback);

    if ((rc == SOCKET_ERROR) && (WSA_IO_PENDING != (err = WSAGetLastError())))
    {
        fprintf(stderr, "WSASend failed: %d\n", err);
        exit(EXIT_FAILURE);
    }
}

Игра в ожидании

Теперь мы можем делать все, что пожелаем, пока всезагрузка чанков.

Примечание: поток, который вызвал WSASend, должен регулярно переводиться в состояние оповещения , чтобы наш обратный вызов "передача завершена" (SendCompletionCallback) исключен из списка APC (асинхронный вызов процедуры).

В моем коде я непрерывно зацикливался до g_nUploadChunks == g_nChunksCompleted.Это должно показать прогресс и скорость загрузки конечного пользователя (может быть изменено, чтобы показать расчетное время завершения, истекшее время и т. Д.)

Примечание 2: этот код использует Plat_FloatTime каквторой счетчик, замените его на любой второй таймер, который использует ваш код (или отрегулируйте соответствующим образом)

g_flLastUploadTimeReset = Plat_FloatTime();

// Clear the line on the screen with some default data
printf("(0 chunks of %d) Upload speed: ???? KiB/sec", g_nUploadChunks);

// Keep looping until ALL upload chunks have completed
while(g_nChunksCompleted < g_nUploadChunks)
{
    // Wait for 10ms so then we aren't repeatedly updating the screen
    SleepEx(10, TRUE);

    // Updata chunk count
    printf("\r(%d chunks of %d) ", g_nChunksCompleted, g_nUploadChunks);

    // Not enough time passed?
    if(g_flLastUploadTimeReset + 1 > Plat_FloatTime())
        continue;

    // Reset timer
    g_flLastUploadTimeReset = Plat_FloatTime();

    // Calculate how many kibibytes have been transmitted in the last second
    float flByteRate = g_nBytesSent/1024.0f;
    printf("Upload speed: %.2f KiB/sec", flByteRate);

    // Reset byte count
    g_nBytesSent = 0;
}

// Delete overlapped data (not used anymore)
delete [] g_pSendOverlapped;

// Note that the transfer has completed
Msg("\nTransfer completed successfully!\n");

Заключение

Я действительно надеюсь, что это помогло кому-то в будущем, кто хотел рассчитать загрузкускорость их TCP сокетов без каких-либо модификаций на стороне сервера.Я понятия не имею, насколько отрицательно сказывается производительность SO_SNDBUF = 0, хотя я уверен, что гуру сокетов это укажет.

2 голосов
/ 30 июня 2010

Вы можете получить нижнюю границу для объема данных, полученных и подтвержденных, вычитая значение опции сокета SO_SNDBUF из числа байтов, которые вы записали в сокет. Этот буфер можно настроить с помощью setsockopt, хотя в некоторых случаях ОС может выбрать длину, меньшую или большую, чем вы указали, поэтому вы должны перепроверить после ее установки.

Однако, чтобы получить более точное значение, удаленная сторона должна информировать вас о прогрессе, поскольку winsock не предоставляет API для получения объема данных, ожидающих в данный момент в буфере отправки.

С другой стороны, вы можете реализовать свой собственный транспортный протокол по UDP, но реализация управления скоростью для такого протокола может быть довольно сложной.

1 голос
/ 30 июня 2010

Поскольку у вас нет контроля над удаленной стороной, и вы хотите сделать это в коде, я бы предложил сделать очень простое приближение.Я предполагаю долгую живую программу / соединение.Однократная загрузка будет слишком искажена ARP, поиском DNS, буферизацией сокетов, медленным запуском TCP и т. Д. И т. Д.

Имеется два счетчика - длина очереди в байтах (OB) и количество байтовотправлено (SB):

  • увеличение OB на количество байтов, которое будет отправляться каждый раз при постановке в очередь чанка для загрузки,
  • уменьшение OB и увеличение SB на число, возвращаемое из send(2) (по модулю -1 случаев),
  • для выборки по таймеру как OB, так и SB - либо сохраните их, зарегистрируйте их, либо вычислите среднее значение,
  • вычислите выдающиеся байты в секунду / минуту /что угодно, то же самое для отправленных байтов.

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

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

Для одноразовых экспериментов просто делайте периодические снимки *Выведите 1021 * (или что-то еще в Windows) и вычислите скорость отправки вручную.

Надеюсь, это поможет.

0 голосов
/ 30 июня 2010

Если ваше приложение использует заголовки пакетов, такие как

0001234DT

, где 000123 - длина пакета для одного пакета, вы можете использовать MSG_PEEK + recv (), чтобы получить длину пакета, прежде чем вы фактически прочитаете его с помощью recv ().

Проблема в том, что send () НЕ выполняет то, что вы думаете - она ​​буферизируется ядром.

getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &flag, &sz));
fprintf(STDOUT, "%s: listener socket send buffer = %d\n", now(), flag);
sz=sizeof(int);
ERR_CHK(getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &flag, &sz));
fprintf(STDOUT, "%s: listener socket recv buffer = %d\n", now(), flag);

Посмотрите, что это шоу для вас.

Когда вы записываете на неблокирующем сокете, в котором есть данные, у него обычно нет МБ данных, припаркованных в буфере, готовом к восстановлению. Большая часть того, что я испытал, состоит в том, что сокет имеет ~ 1500 байт данных на одну запись. Поскольку вы, вероятно, читаете в блокирующем сокете, для завершения recv () требуется некоторое время.

Размер буфера сокетов является, вероятно, единственным лучшим предиктором пропускной способности сокетов. setsockopt () позволяет вам изменять размер буфера сокета с точностью до точки. Примечание. Эти буферы распределяются между сокетами во многих ОС, таких как Solaris. Вы можете убить производительность, переставляя эти настройки слишком сильно.

Кроме того, я не думаю, что вы измеряете то, что вы думаете, что измеряете. Реальная эффективность send () - это мера пропускной способности на конце recv (). Не конец send (). ИМО.

...