Calculating Socket Download Speed

I am wondering if anyone knows how to calculate the load speed of a Berkeley socket in C ++. My send request is not blocked and takes 0.001 seconds to send 5 megabytes of data, but it takes some time to return a response (so I know its download).

This is a TCP socket for an HTTP server, and I need to check as many data bytes loaded / left asynchronously. However, I cannot find any API functions for this in Winsock, so I'm at a dead end.

Any help would be greatly appreciated.

EDIT: I found a solution and will ship it as soon as possible!

EDIT 2: The correct solution added as an answer will be added as a solution after 4 hours.

+4
source share
4 answers

I solved the problem thanks to bdolan , suggesting to reduce SO_SNDBUF . However, to use this code, you should notice that your code uses Winsock 2 (for overlapped sockets and WSASend ). In addition to this, your SOCKET descriptor should be created similarly:

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

Note the WSA_FLAG_OVERLAPPED flag as the final parameter.

In this answer, I will talk about the steps of uploading data to a TCP server and tracking each download fragment and its completion status. This concept requires dividing your load buffer into chunks (requires minimal existing code modification) and loading it in parts, and then tracking each fragment.

My code stream

Global variables

Your code document should have the following global variables:

 #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; 

Note: in my tests, decreasing UPLOAD_CHUNK_SIZE increases the accuracy of the download speed, but reduces the overall download speed. Increasing UPLOAD_CHUNK_SIZE reduces the accuracy of the download speed, but increases the overall download speed. 4 kilobytes (4096 bytes) was good composition for a ~ 500 KB file.

Callback function

This function increases the bytes sent and completed completed variables (called after the fragment has been fully loaded on the server)

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

Prepare socket

Initially, the socket should be prepared by decreasing SO_SNDBUF to 0.

Note: In my tests, any value greater than 0 will lead to unwanted behavior.

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

Create an array of WSAOVERLAPPED

It is necessary to create an array of WSAOVERLAPPED structures in order to maintain the overlapping status of all our pieces of loading. For this, I simply:

 // 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); 

Data loading

All data that needs to be sent, for example, targets, is stored in a variable called pszData . Then, using WSASend , the data is sent in blocks defined by the constant, 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); } } 

Waiting game

Now we can do whatever we wish while all the pieces are loading.

Note. a thread called WSASend should be placed in a warning state regularly so that our β€œcompleted” translation is completed (SendCompletionCallback) is unloaded from the APC list (asynchronous procedure call).

In my code, I loop continuously into g_nUploadChunks == g_nChunksCompleted . This should show the progress and download speed of the end user (can be changed to show the approximate completion time, elapsed time, etc.).

Note 2: this code uses Plat_FloatTime as the second counter, replace it with any second timer used by your code (or adjust accordingly)

 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"); 

Conclusion

I really hope this helps someone in the future who wants to calculate the download speed on their TCP sockets without any server-side modifications. I have no idea how bad the performance of SO_SNDBUF = 0 , although I'm sure the socket guru will point it out.

+4
source

You can get the lower bound on the amount of data received and confirmed by subtracting the value of the SO_SNDBUF parameter from the number of bytes that you wrote to the socket. This buffer can be configured using setsockopt , although in some cases the OS may choose a length shorter or longer than you specify, so you should re-check it after installing it.

To get more accurate information, you should report that the far side will inform you of the progress, since winsock does not provide an API to get the amount of data that is currently expected in the send buffer.

Alternatively, you can implement your own transport protocol in UDP, but implementing speed control for such a protocol can be quite complicated.

+2
source

If your application uses package headers like

0001234DT

where 000123 is the packet length for one packet, you can use MSG_PEEK + recv () to get the packet length before you read it with recv ().

The send () problem does NOT do what you think - it is buffered by the kernel.

 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); 

See what they show for you.

When you return to a non-blocking socket that has data, it usually does not have MB of data parked in buufer ready for recv. Most of what I experienced is that the socket has ~ 1500 bytes of data per recv. Since you are probably reading on a blocking socket, it takes some time to complete recv ().

The Socket buffer size is perhaps the single best predictor of socket bandwidth. setsockopt () allows you to change the size of the socket buffer, right down to the point. Note. These buffers are shared between sockets on many operating systems, such as Solaris. You can kill performance by changing these settings too much.

Also, I do not think that you are measuring what you think you are measuring. The actual performance of send () is a measure of throughput at the end of recv (). Not the end of send (). IMO

0
source

Since you do not have control over the remote side, and you want to do this in code, I would suggest making a very simple approximation. I assume that you live a long time. One-time downloads will be too distorted by ARP, DNS lookups, socket buffering, slow TCP startup, etc. Etc.

You have two counters: the length of the remaining queue in bytes (OB) and the number of bytes sent (SB):

  • increases the OB by the number of bytes sent each time you set the fragment to load,
  • decreasing OB and incrementing SB by the number returned from send(2) (modulo -1 ),
  • on the sample timer, both OB and SB - either save them, or write them down, or calculate the current average value,
  • compute outstanding bytes per second / minute / independently, the same for sent bytes.

The network stack does the buffering, and TCP does the retransmission and flow control, but that doesn't really matter. These two counters will tell you the speed with which your applications will receive data, and the speed that it can push to the network. This is not a way to find out the real speed of communication, but a way to preserve useful indicators about how well the application works.

If the data transfer rate is lower, the network output speed is all right. If it's the other way around, and the network cannot keep up with the application - there is a problem - you need a faster network, a slower application, or a different design.

For one-time experiments, just take periodic snapshots of netstat -sp tcp output (or something else on Windows) and calculate the sending speed manually.

Hope this helps.

0
source

Source: https://habr.com/ru/post/1314276/


All Articles