计算套接字上传速度

时间:2010-06-30 01:18:10

标签: c++ sockets performance winsock

我想知道是否有人知道如何计算C ++中Berkeley套接字的上传速度。我的发送呼叫没有阻塞,需要0.001秒才能发送5兆字节的数据,但需要一段时间来 recv 响应(所以我知道它正在上传)。

这是HTTP服务器的TCP套接字,我需要异步检查已上载/剩余的数据字节数。但是,我在Winsock中找不到任何API函数,所以我很难过。

非常感谢任何帮助。

编辑:我找到了解决方案,并会尽快发布作为答案!

编辑2:正确的解决方案作为答案添加,将在4小时内作为解决方案添加。

4 个答案:

答案 0 :(得分:5)

由于 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会降低上传速度的准确性,但会提高整体上传速度。对于文件大小为500kB,4千字节(4096字节)是一个很好的比较。

回调功能

此函数增加发送的字节数和块完成的变量(在块完全上传到服务器后调用)

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的线程必须定期放入alertable state,以便我们的'传输完成'回调(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是多少,虽然我确信套接字大师会指出这一点。

答案 1 :(得分:2)

您可以通过从写入套接字的字节数中减去SO_SNDBUF socket option的值来获得接收和确认的数据量的下限。可以使用setsockopt调整此缓冲区,但在某些情况下,操作系统可能会选择比您指定的长度更小或更大的长度,因此您必须在设置后重新检查。

然而,为了更加精确,您必须让远程端通知您进度,因为winsock不会公开API以检索发送缓冲区中当前待处理的数据量。

或者,您可以在UDP上实现自己的传输协议,但是对这样的协议实施速率控制可能非常复杂。

答案 2 :(得分:1)

由于您无法控制远程端,并且您希望在代码中执行此操作,因此我建议您进行非常简单的近似。我假设一个长期的生活程序/连接。一次性上传会因ARP,DNS查询,套接字缓冲,TCP慢启动等而过于倾斜等。

有两个计数器 - 未完成队列的长度(字节)(OB)和发送的字节数(SB):

  • 每次将一个块排队等待上传时,按每个字节数递增OB,
  • 递减OB并将SB增加send(2)返回的数字(模-1个案例),
  • 在OB和SB的计时器样本上 - 存储它们,记录它们或计算运行平均值,
  • 计算未完成的字节,每秒/分钟/不同,发送的字节数相同。

网络堆栈执行缓冲,TCP执行重新传输和流量控制,但这并不重要。这两个计数器将告诉您应用程序生成数据的速率,以及它能够将数据推送到网络的速率。这不是找出真实链接速度的方法,而是一种保持应用程序运行状况的有用指标的方法。

如果数据生成率低于网络输出率 - 一切都很好。如果是相反的情况并且网络无法跟上应用程序 - 那么就会出现问题 - 您需要更快的网络,更慢的应用程序或不同的设计。

对于一次性实验,只需拍摄netstat -sp tcp输出(或Windows上的任何内容)的定期快照,然后手动计算发送速率。

希望这有帮助。

答案 3 :(得分:0)

如果您的应用使用

等数据包标头

0001234DT

其中000123是单个数据包的数据包长度,在使用recv()实际读取数据包之前,可以考虑使用MSG_PEEK + 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);

了解这些内容适合您。

当你收到有数据的非阻塞套接字时,它通常没有准备好恢复的buufer中停放的数据MB。我经历的大部分内容是套接字每个recv有大约1500字节的数据。由于您可能正在读取阻塞套接字,因此recv()需要一段时间才能完成。

套接字缓冲区大小可能是套接字吞吐量的最佳预测器。 setsockopt()允许您更改套接字缓冲区大小,直到某一点。注意:这些缓冲区在许多操作系统(如Solaris)的套接字之间共享。您可以通过过多地调整这些设置来消除性能。

另外,我认为你不是在测量你认为你在测量的东西。 send()的实际效率是recv()端吞吐量的度量。不是send()结束。 IMO。