Я решил свою проблему благодаря 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
, хотя я уверен, что гуру сокетов это укажет.