Back  | Code Snippets |  [Bill's Home]

Time networking events

Code

Millisecond timing

Many applications require accurate timing, such as the time that a data packet takes to transverse a network, or the time that a user takes between keystrokes. Win32 has a whole host of timing functions which can be used to accurately measure time.

- GetCurrentTime. Returns clock ticks since Windows started.
- GetTickCount. Returns clock ticks since Windows started.
- KillTimer. Destroys a timer.
- QueryPerformanceCounter.
- QueryPerformanceFrequency.
- SetTimer. Creates a timer.

The GetTickCount can be used in applications which only require a preciseness of 1 ms, whereas QueryPerformanceCounter can be used in higher resolution applications (if supported by the hardware). Figure 1 shows an outline design in Delphi, where a button is used to start a timer, and one is used to end a timer. Program 1 show an outline program.

 

 


Basic timer

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Label1: TLabel;
Button2: TButton;
Edit2: TEdit;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
start: word;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
start:=GetTickCount;
edit1.text:='Started...';
end;

procedure TForm1.Button2Click(Sender: TObject);
var endval:word;
begin
endval:=GetTickCount;
edit1.text:=inttostr(endval-start) + ' ms';
end;

procedure TForm1.FormCreate(Sender: TObject);
var secs:integer;
begin
secs:=gettickcount div 1000;
edit2.text:=inttostr(secs div 60) + ' mins';
end;

end.

Thus we can measure events to a precision of milliseconds with the GetTickCount function. This is achieved by setting a start variable at the start of the event, and then when the event occurs the endval is taken. The difference between the two gives the time difference.

High-precision timing (microseconds)

The QueryPerformance Counter can determine the number of milliseconds since the system started, and uses a 64-bit wide unsigned value to get the best possible resolution. Along with this the QueryPerformanceFrequency can be used to determine the resolution of the high-resolution counter. Intially the counter is tested to see if it can support the high-resolution, with:

Testing timer, and getting a count value

If QueryPerformanceCounter(Counter1) Then


This will also get a 64-bit integer value (int64), into Counter1. The resolution of the timer can be determined from:

Testing resolution

QueryPerformanceFrequency(Freq);


Which returns the number of increments made by the timer, every second. Thus it gives the frequency of the timer. Again this returns a 64-bit integer value. Program 2 gives example code which will determine the resolution of the timer, and will also time the amount of time taken by the API call. In Figure 2 it can be seen that, in this case, that the resolution of the timer is 0.27937 uS (microseconds), and that the timer is operating at 3,579,545 Hz. It can also be seen that the time to complete the API call is 2.51429 uS (microseconds).

Thus we can get timings of less than 1 millionth of a second using this timer. Again to time an event, we would set an initial counter value (such as counter1), and then when the event had occurred we take the new time count, and compare them.


 

Precise timer

unit timer2;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var Counter1, Counter2, freq:int64;
res, t: real;
str:string;
begin
If QueryPerformanceCounter(Counter1) Then
begin
QueryPerformanceCounter(Counter2);
QueryPerformanceFrequency(Freq);
memo1.Lines.add('Minimum resolution: 1/' + inttostr(Freq)
+ ' sec');
str:=format('%8.5f', [1/Freq/1e-6]);
memo1.Lines.add('Minimum resolution: ' + str + ' us');
t:=(counter2-counter1)/freq;
str:=format('%8.5f', [t/1e-6]);
memo1.Lines.add('API Overhead: ' + str + ' us');
end
Else
memo1.Lines.add('High-resolution counter not supported.');

end;

end.

Timing network events

A good example of using the high precision timers is to time network events. For example we could time the amount of time that a client takes to make a connection with a server. This is an important factor, and obviously depends on the amount of traffic on the network, and also on the loading of the server.

Program 2 shows a sample program which measures the amount of time it takes for a client to make a connection with a server. The highlighed text is the code which has been added. Initially the start value is set when the connection is requested, and the end time is set when the SocketEvent is generated. A sample run is shown in Figure 3. It can be seen from this that the connection time is 499.8 ms.

 

 

Precise timer

unit client3;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms,Dialogs, ScktComp, StdCtrls;

type
TForm1 = class(TForm)
ClientSocket: TClientSocket;
Label1: TLabel;
Label2: TLabel;
GroupBox1: TGroupBox;
Edit3: TEdit;
Label3: TLabel;
Label5: TLabel;
Button1: TButton;
Memo2: TMemo;
GroupBox2: TGroupBox;
CheckBox1: TCheckBox;
Label4: TLabel;
Label6: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Label7: TLabel;
Edit5: TEdit;
Button2: TButton;
ComboBox1: TComboBox;
Edit4: TEdit;
Label8: TLabel;

procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);
procedure Button1Click(Sender: TObject);
procedure ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
start: int64;


implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var port:integer;
begin
ClientSocket.Host:=edit3.text;
ClientSocket.ClientType:=ctNonBlocking;
ClientSocket.Active:=true;

QueryPerformanceCounter(start); // get start
end;

procedure TForm1.ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
var endcount, freq: int64;
t:real;
str: string;

begin
Checkbox1.Checked:=true;
Edit1.Text:=Socket.LocalAddress;
Edit2.Text:=Socket.LocalHost;
Memo2.Lines.Clear;
edit5.Text:='';

QueryPerformanceCounter(endcount);
QueryPerformanceFrequency(Freq);
t:=(endcount-start)/freq;
str:=format('%8.5f ms', [t/1e-3]);
edit4.text:=str;

end;

procedure TForm1.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
ClientSocket.Socket.SendText(edit5.text +
chr(10)+chr(13)+chr(10)+ chr(13));
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
ComboBox1.items.add('Echo'); // Port 7
ComboBox1.items.add('Daytime'); // Port 13
ComboBox1.items.add('Char gen'); // Port 19
ComboBox1.items.add('FTP'); // Port 21
ComboBox1.items.add('Telnet'); // Port 23
ComboBox1.items.add('STMP'); // Port 25
ComboBox1.items.add('WWW'); // Port 80
ComboBox1.items.add('Test (1001)'); // Port 1001
end;

procedure TForm1.ComboBox1Change(Sender: TObject);
var val:integer;
begin
val:=ComboBox1.ItemIndex;
if (val=0) then ClientSocket.Port:=7
else if (val=1) then ClientSocket.Port:=13
else if (val=2) then ClientSocket.Port:=19
else if (val=3) then ClientSocket.Port:=21
else if (val=4) then ClientSocket.Port:=23
else if (val=5) then ClientSocket.Port:=25
else if (val=6) then ClientSocket.Port:=80
else ClientSocket.Port:=1001
end;

end.

 

 

Ref: Extract from Chapter 15, Mastering Delphi, W.Buchanan, Palgrave, 2002.