5


2

ソケットアップロード速度の計算

C ++でBerkeleyソケットのアップロード速度を計算する方法を知っている人がいるかどうか疑問に思っています。 私の_send_呼び出しはブロックされておらず、5メガバイトのデータを送信するのに0.001秒かかりますが、応答を_recv_するのに少し時間がかかります(したがって、アップロードしていることがわかります)。

これはHTTPサーバーへのTCPソケットであり、アップロードされた/残っているデータのバイト数を非同期で確認する必要があります。 ただし、WinsockでこのためのAPI関数が見つからないため、困惑しています。

任意の助けは大歓迎です。

*編集:*解決策を見つけました。できるだけ早く回答として投稿します!

*編集2:*回答として追加された適切なソリューションは、4時間以内にソリューションとして追加されます。

4 Answer


5


「SO_SNDBUF」の削減を提案する* bdolan *のおかげで問題を解決しました。 ただし、このコードを使用するには、コードがWinsock 2を使用していることに注意する必要があります(重複ソケットと「WSASend」の場合)。 これに加えて、 `SOCKET`ハンドルは次のように作成されている必要があります。

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

最終パラメーターとして「WSA_FLAG_OVERLAPPED」フラグに注意してください。

この回答では、データをTCPサーバーにアップロードし、各アップロードチャンクとその完了ステータスを追跡する段階を実行します。 この概念では、アップロードバッファーをチャンクに分割し(既存のコードの最小限の変更が必要)、それを1つずつアップロードしてから、各チャンクを追跡する必要があります。

私のコードフロー

グローバル変数

コードドキュメントには、次のグローバル変数が必要です。

#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バイト)は、サイズが最大500kBのファイルに適しています。

コールバック関数

この関数は、送信されたバイトとチャンクが完了した変数をインクリメントします(チャンクがサーバーに完全にアップロードされた後に呼び出されます)

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`を呼び出したスレッドは、定期的にhttp://msdn.microsoft.com/en-us/library/aa363772%28VS.85%29.aspx[alertable state]に配置する必要があります。 「転送完了」コールバック(SendCompletionCallback)は、APC(非同期プロシージャコール)リストからデキューされます。

私のコードでは、 `g_nUploadChunks == g_nChunksCompleted`まで継続的にループしました。 これは、エンドユーザーのアップロードの進行状況と速度を表示することです(推定完了時間、経過時間などを表示するように変更できます)。

*注2:*このコードは、 `Plat_FloatTime`を2番目のカウンターとして使用します。これを、コードで使用する2番目のタイマーに置き換えます(または適宜調整します)

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


SO_SNDBUFソケットオプションの値を減算することにより、受信および確認されるデータ量の下限を取得できます。 ソケットに書き込んだバイト数から。 このバッファは、 `setsockopt`を使用して調整できますが、場合によっては、OSが指定した長さよりも小さいまたは大きい長さを選択することがあるため、設定後に再確認する必要があります。

ただし、それよりも正確にするには、送信バッファで現在保留中のデータ量を取得するAPIをwinsockが公開しないため、リモート側に進捗状況を通知する必要があります。

または、UDPに独自のトランスポートプロトコルを実装することもできますが、そのようなプロトコルのレート制御の実装は非常に複雑になる可能性があります。


1


リモート側を制御することはできず、コードでそれを実行したいので、非常に単純な近似を行うことをお勧めします。 私は長生きするプログラム/接続を想定しています。 ワンショットアップロードは、ARP、DNSルックアップ、ソケットバッファリング、TCPスロースタートなどによって過度に歪められます。 etc.

2つのカウンターがあります-未処理キューの長さ(バイト)(OB)と送信バイト数(SB):

  • エンキューするたびに送信されるバイト数でOBをインクリメントします アップロード用のチャンク、

  • OBをデクリメントし、SBを `send(2)`から返された数だけインクリメントします (モジュロ「-1」の場合)、

  • OBとSBの両方のタイマーサンプルで、保存、ログ記録、または 移動平均の計算、

  • 未送信のバイトを1秒/分/何でも送信します。 バイト。

ネットワークスタックはバッファリングを行い、TCPは再送信とフロー制御を行いますが、それは実際には重要ではありません。 これらの2つのカウンターは、アプリがデータを生成するレートと、それをネットワークにプッシュできるレートを示します。 これは、実際のリンク速度を調べる方法ではなく、アプリのパフォーマンスに関する有用な指標を保持する方法です。

データ生成率がネットワーク出力率を下回っている場合は、すべて問題ありません。 逆に、ネットワークがアプリに追いつかない場合-問題があります-より高速なネットワーク、より遅いアプリ、または異なるデザインが必要です。

1回限りの実験では、「netstat -sp tcp」出力(またはWindows上にあるもの)の定期的なスナップショットを作成し、手動で送信レートを計算します。

お役に立てれば。


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などの多くのOSのソケット間で共有されます。 これらの設定を調整しすぎると、パフォーマンスが低下する可能性があります。

また、あなたがあなたが測定していると思うものを測定しているとは思わない。 send()の実際の効率は、recv()側のスループットの測定値です。 send()終了ではありません。 IMO.