Title: Data Transfer Formulae
Question: What is the difference between KBps and Kbps? What is the difference between bits, bytes and baud? How do I determine the data transfer rate? How do I figure out how long it will take to download a file at a particular transfer rate? How do I calculate, in real-time, the time remaining in a download?
Answer:
First off, I would like to clear up some confusion regarding KBps and Kbps (lower case b). KBps stands for kilobytes per second, where Kbps stands for kilobits per second. Where 1 kilobyte (KB) = 8 kilobits (Kb).
When it comes to transfer rate, speed is measured in Kbps. Thus, a modem with a transfer rate of 33.6K (33600 bps) transfers data at 4.2 KBps (4.2 kilobytes per second). Here, the differences between KB and Kb become enormous, and immediately we can see why modem users become confused as to why it takes so long to transfer data. While they think they are transferring at 33.6 KBps, they are, in reality, connected at 33.6 Kbps and transferring at 4.2 KBps (33.6 Kb / 8 = 4.2). Thus, every 33.6K of data will not take 1/Sec to download but 8/Sec.
I would also like to take this time to clear up the confusion surrounding the word baud. While commonly used in reference as to how many bits per second a modem can transfer data, this is incorrect. Baud is a measure of how frequently sound changes on a phone line. So, depending on the modem in use, the amount of bits that can be transmitted, versus the changes in sound required to make that transmission, varies.
A working example follows (source code here), using the NetMasters TNMHTTP component. However, I felt it imperative to break the steps down for you first, as, depending on the TCP/IP component you are using, the naming conventions of variables and events will vary, where as proven formulae can be applied affectively in any given situation.
Formulae in use:
bps = bytes transferred in 1 second
KBps (KB/Sec) = bps / 1024
Kbps (Kb/Sec) = KBps x 8
Steps taken:
1. Store the start time in a variable: nStartTime := GetTickCount;
2. Store the file size (KB) in a variable: nFileSize := "File Size";
3. Begin data transfer.
4. Update bytes transferred: Inc(nBytesTransferred, nNewBytes);
5. Get elapsed time: nTimeElapsed := (GetTickCount - nStartTime) / 1000;
6. Calculate bps: nBps := BytesTransferred / nTimeElapsed;
7. Calculate KBps: nKBps := nBps / 1024;
Using the data:
Total download time (seconds) := nFileSize / nKBps;
bps := FloatToStr(nBps);
KB/Sec (KBps) := FloatToStr(nKBps);
Seconds remaining := FloatToStr(((nFileSize - BytesTransferred) / 1024) / KBps);
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Gauges, Psock, NMHttp;
type
TfMain = class(TForm)
Label1: TLabel;
eURL: TEdit;
bGet: TButton;
lbMessages: TListBox;
gbDetails: TGroupBox;
lEstimate: TLabel;
lKBps: TLabel;
lReceived: TLabel;
lRemaining: TLabel;
gProgress: TGauge;
NMHTTP1: TNMHTTP;
lbps: TLabel;
bCancel: TButton;
procedure NMHTTP1PacketRecvd(Sender: TObject);
procedure bGetClick(Sender: TObject);
procedure bCancelClick(Sender: TObject);
procedure NMHTTP1Connect(Sender: TObject);
procedure NMHTTP1ConnectionFailed(Sender: TObject);
procedure NMHTTP1Disconnect(Sender: TObject);
procedure NMHTTP1Failure(Cmd: CmdType);
procedure NMHTTP1HostResolved(Sender: TComponent);
procedure NMHTTP1InvalidHost(var Handled: Boolean);
procedure NMHTTP1Status(Sender: TComponent; Status: String);
procedure NMHTTP1Success(Cmd: CmdType);
private
{ Private declarations }
function ss2nn(Seconds: Integer): String;
public
{ Public declarations }
end;
var
fMain: TfMain;
nFileSize: Double;
nStartTime: DWord;
implementation
{$R *.DFM}
{The purpose of this function is to determine how many minutes and seconds there
are in a given number of seconds}
function TfMain.ss2nn(Seconds: Integer): String;
var
nMin, nSec: Integer;
begin
{Check for less than 1/Min}
if Seconds then Result := '0 minutes ' + IntToStr(Seconds) + ' seconds'
else begin
{Determine minutes}
nMin := Seconds div 60;
{Determine seconds}
nSec := Seconds - (nMin * 60);
{Return Result}
Result := IntToStr(nMin) + ' minutes ' + IntToStr(nSec) + ' seconds';
end;
end;
procedure TfMain.NMHTTP1PacketRecvd(Sender: TObject);
var
nBytesReceived, nTimeElapsed, nBps, nKBps: Double;
begin
{The following is performed only once, upon reception of the first packet}
if nFileSize NMHTTP1.BytesTotal then
begin
{Get File Size}
nFileSize := NMHTTP1.BytesTotal;
{Calculate transfer time based on 33.6 Kbps connection}
lEstimate.Caption := 'Estimated download time at 33.6 Kbps: ' + ss2nn(Round(
(nFileSize / 1024) / 4.2));
{Get Start Time}
nStartTime := GetTickCount;
end;
{Update nBytesReceived}
nBytesReceived := NMHTTP1.BytesRecvd;
{Calculate the amount of seconds passed since the transfer started}
nTimeElapsed := (GetTickCount - nStartTime) / 1000;
{Check for 0/Sec elapsed, setting default if so, to prevent division by zero}
if nTimeElapsed = 0 then nTimeElapsed := 1;
{Calculate bytes per second}
nBps := nBytesReceived / nTimeElapsed;
{Calculate Kilobytes per second}
nKBps := nBps / 1024;
{Update Controls}
gProgress.Progress := Round((nBytesReceived * 100) / nFileSize);
lbps.Caption := IntToStr(Round(nBps * 8)) + ' bits per second';
lKBps.Caption := IntToStr(Round(nKBps)) + ' KB/Sec (KBps)';
lReceived.Caption := FloatToStr(nBytesReceived) + ' of ' + FloatToStr(
nFileSize) + ' bytes received';
lRemaining.Caption := ss2nn(Round(((nFileSize - nBytesReceived) / 1024) /
nKBps)) + ' remaining';
end;
procedure TfMain.bGetClick(Sender: TObject);
begin
{Reset Variables}
nFileSize := 0;
{Reset Controls}
lbMessages.Clear;
gProgress.Progress := 0;
lEstimate.Caption := 'Estimated download time at 33.6 Kbps: 0 minutes 0 ' +
'seconds';
lbps.Caption := '0 bits per second';
lKBps.Caption := '0 KB/Sec (KBps)';
lReceived.Caption := '0 of 0 bytes received';
lRemaining.Caption := '0 minutes 0 seconds remaining';
{Get File}
NMHTTP1.Get(eURL.Text);
end;
procedure TfMain.bCancelClick(Sender: TObject);
begin
{Disconnect from Host}
NMHTTP1.Disconnect;
{Update lbMessages}
lbMessages.Items.Append('Get Canceled');
lbMessages.Items.Append('Disconnected');
end;
procedure TfMain.NMHTTP1Connect(Sender: TObject);
begin
{Disable/Enable Controls}
bGet.Enabled := False;
bCancel.Enabled := True;
{Work with lbMessages}
with lbMessages.Items do
begin
Append('Connected');
Append('Local Address: ' + NMHTTP1.LocalIP);
Append('Remote Address: ' + NMHTTP1.RemoteIP);
end;
end;
procedure TfMain.NMHTTP1ConnectionFailed(Sender: TObject);
begin
ShowMessage('Connection Failed.');
end;
procedure TfMain.NMHTTP1Disconnect(Sender: TObject);
begin
{Disable/Enable Controls}
bCancel.Enabled := False;
bGet.Enabled := True;
{Update lbMessages}
if NMHTTP1.Connected then lbMessages.Items.Append('Disconnected');
end;
procedure TfMain.NMHTTP1Failure(Cmd: CmdType);
begin
case Cmd of
CmdGET : lbMessages.Items.Append('Get Failed');
CmdOPTIONS: lbMessages.Items.Append('Options Failed');
CmdHEAD : lbMessages.Items.Append('Head Failed');
CmdPOST : lbMessages.Items.Append('Post Failed');
CmdPUT : lbMessages.Items.Append('Put Failed');
CmdPATCH : lbMessages.Items.Append('Patch Failed');
CmdCOPY : lbMessages.Items.Append('Copy Failed');
CmdMOVE : lbMessages.Items.Append('Move Failed');
CmdDELETE : lbMessages.Items.Append('Delete Failed');
CmdLINK : lbMessages.Items.Append('Link Failed');
CmdUNLINK : lbMessages.Items.Append('UnLink Failed');
CmdTRACE : lbMessages.Items.Append('Trace Failed');
CmdWRAPPED: lbMessages.Items.Append('Wrapped Failed');
end;
end;
procedure TfMain.NMHTTP1HostResolved(Sender: TComponent);
begin
lbMessages.Items.Append('Host Resolved');
end;
procedure TfMain.NMHTTP1InvalidHost(var Handled: Boolean);
begin
ShowMessage('Invalid Host. Please specify a new URL.');
end;
procedure TfMain.NMHTTP1Status(Sender: TComponent; Status: String);
begin
if NMHTTP1.ReplyNumber = 404 then ShowMessage('Object Not Found.');
end;
procedure TfMain.NMHTTP1Success(Cmd: CmdType);
begin
case Cmd of
{Make sure Get was not canceled}
CmdGET: if NMHTTP1.Connected then lbMessages.Items.Append('Get Succeeded');
CmdOPTIONS: lbMessages.Items.Append('Options Succeeded');
CmdHEAD : lbMessages.Items.Append('Head Succeeded');
CmdPOST : lbMessages.Items.Append('Post Succeeded');
CmdPUT : lbMessages.Items.Append('Put Succeeded');
CmdPATCH : lbMessages.Items.Append('Patch Succeeded');
CmdCOPY : lbMessages.Items.Append('Copy Succeeded');
CmdMOVE : lbMessages.Items.Append('Move Succeeded');
CmdDELETE : lbMessages.Items.Append('Delete Succeeded');
CmdLINK : lbMessages.Items.Append('Link Succeeded');
CmdUNLINK : lbMessages.Items.Append('UnLink Succeeded');
CmdTRACE : lbMessages.Items.Append('Trace Succeeded');
CmdWRAPPED: lbMessages.Items.Append('Wrapped Succeeded');
end;
end;
end.