Delphi Programming

25
Developing TCP/IP-based Server Applications using Indy Components Christian Wilkerson Introduction - What is Indy (A User’s Perspective) I imagine that everyone interested in the topic of using Indy components to develop servers already knows something about them. But for those that do not, let’s take a brief moment to enlighten you. Indy is short for Internet Direct. Indy is a set of Open Source Delphi Components that allow developers to write network based software. Actually, if you check out the website www.indyproject.org , you will find that the components I am refering to are now called Indy.Sockets. However, if you go way back, you will find that these components got their start long ago as a project called WinShoes. A kind of funny name given that they were a wrapper around Microsoft’s Winsock.dll. WinShoes cover Winsock. Get it? The original founder of this project, Chad Z. Hower, aka Kudzu, gets credit for that one. Our company chose Indy because… Actually, let me set the record straight. I chose Indy for our company because it was something I had been using for pet projects since it was called WinShoes. I found it to pretty straight forward to use, and loved the fact that it was FREE! As for my company, we are in the security business. Some of our customers you have heard of i.e. the White House, and Homeland Security. We also have others that do not officially exist i.e. Top Secret, Could Tell You but Then I’d Have to Kill You. There are also commercial customers like banks, casinos, and universities. All of these customers require 24/7 reliability. Our company went through a process of having our developers who felt particularly strong towards one set of components or another

description

programiranje u delphi-ju

Transcript of Delphi Programming

Developing TCP/IP-based Server Applications using Indy Components

Developing TCP/IP-based Server Applications using Indy ComponentsChristian Wilkerson

Introduction - What is Indy (A Users Perspective)I imagine that everyone interested in the topic of using Indy components to develop servers already knows something about them. But for those that do not, lets take a brief moment to enlighten you. Indy is short for Internet Direct. Indy is a set of Open Source Delphi Components that allow developers to write network based software. Actually, if you check out the website www.indyproject.org, you will find that the components I am refering to are now called Indy.Sockets. However, if you go way back, you will find that these components got their start long ago as a project called WinShoes. A kind of funny name given that they were a wrapper around Microsofts Winsock.dll. WinShoes cover Winsock. Get it? The original founder of this project, Chad Z. Hower, aka Kudzu, gets credit for that one. Our company chose Indy because Actually, let me set the record straight. I chose Indy for our company because it was something I had been using for pet projects since it was called WinShoes. I found it to pretty straight forward to use, and loved the fact that it was FREE! As for my company, we are in the security business. Some of our customers you have heard of i.e. the White House, and Homeland Security. We also have others that do not officially exist i.e. Top Secret, Could Tell You but Then Id Have to Kill You. There are also commercial customers like banks, casinos, and universities. All of these customers require 24/7 reliability. Our company went through a process of having our developers who felt particularly strong towards one set of components or another write demo apps. The demo app utilizing Indy components (written by me) was the quickest and most reliable. Now that we are all caught up, lets move on to developing some server applications.Using TidHTTPServer

Shipping with Delphi 2005 is Indy.Sockets version 10. It contains 45 different server components allowing you to develop all kinds of different servers for standard protocols. For instance, simply drop a TidHTTPServer component on a form and you are already a long way to developing your own webserver. Does the world really need another web server? If you are talking about writing or re-writing Apache, or IIS, or something along those lines, the answer is probably no. However, think about the possibilities for a moment. How about adding the ability to configure server room software remotely? If you have written an enterprise level system, it could be rather inconvenient to go back to the server room every time you need to adjust the server application. In contrast, it would be very convenient to be able to have all of its settings adjustable through a web-browser.To keep things simple, we will write an application with an embedded webserver that allows for it to be configured remotely. To be more specific we will allow a user to remotely set the color of the main form of our server software with their web-browser. STEP 1: Create a new VCL Forms Application (Win 32). Save it as HTTPServerExample.STEP 2: Add Controls - A radio group for color selection, a TidHTTPServer, a TMemo to log activity on the TidHTTPServer, and a checkbox that controls TidHTTPServers Active property. Ultimately it should look like Figure 1.

Figure 1 - HTTPServerExample's main formSTEP 3: Configure properties and add event code to TidHTTPServer.Note: Here you have a choice to have TidHTTPServer use either fibers or threads for the execution of event code. We will discuss this choice in fuller detail later on in the section dedicated to developing new types of TCP based servers.TidHTTPServers Property Settings:

Set the TidHTTPServers Active property equal to True in the Object Inspector.

TidHTTPServers Events:

This is very important to always remember. Even though you have the option of using fibers or threads with your server development, you should still write the event code as thread safe as possible. In this case, the Connect event is using the Windows API call to PostMessage to pass a PChar to a custom message handler in our main form. That handler will translate the PChar into a string that gets passed into a TMemo component. procedure TForm1.IdHTTPServer1Connect(AContext: TIdContext);

var

DBugMsg: PChar;

begin

DBugMsg := StrNew(PChar('New Connection'));

PostMessage(Handle, WM_DBUG, integer(DBugMsg), 0);

end;

Heres the declaration of the custom message handler

procedure WMDBug(var AMsg: TMessage); message WM_DBUG;

Heres the definition of the handler

procedure TForm1.WMDBug(var AMsg: TMessage);

begin

Memo1.Lines.Add(PChar(AMsg.WParam));

StrDispose(PChar(AMsg.WParam));

end;

The only other event handler that we need for the HTTP Server is OnCommandGet. Web-browsers are always requesting pages from web-servers and this is the event handler that will take care of fulfilling that request. However, we do not have any HTML files. The HTML text is actually just built out of a constant in our code.Heres the constant that is used to build the HTML

WEB_PAGE = '' + #13#10 +

'' + #13#10 +

'The Amazing HTTP Server' + #13#10 +

'' + #13#10 +

'' + #13#10 +

'' + 'The color of your magnificent Indy based HTTP ' + 'Server is...' + #13#10 +

'%s' + #13#10 +

'' + 'Please pick a color for the HTTP Server:' +

'' + #13#10 + '' + 'BLUE' + '' + #13#10 + '' +

'' + 'RED' + '' + #13#10 + '' +

'' + 'YELLOW' + '' + #13#10 + '' + #13#10 +

'' + #13#10;

Heres the code for CommandGet event of the HTTP Serverprocedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;

ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);

var

NewColor: TColor;

DBugMsg: PChar;

ColorValue: string;

ColorText: string;

DoColorChange: boolean;

HTMLStrings: TStringList;

begin

NewColor := clBlue;

DoColorChange := False;

// Post the request to the Main form's memo component

DBugMsg := StrNew(PChar(ARequestInfo.Document));

PostMessage(Handle, WM_DBUG, integer(DBugMsg), 0);

// Discover what color change the user wants to see

if Pos('blue', ARequestInfo.Document) > 0 then

begin

NewColor := clBlue;

GFormColorIndex := 0;

DoColorChange := True;

end;

if Pos('red', ARequestInfo.Document) > 0 then

begin

NewColor := clRed;

GFormColorIndex := 1;

DoColorChange := True;

end;

if Pos('yellow', ARequestInfo.Document) > 0 then

begin

NewColor := clYellow;

GFormColorIndex := 2;

DoColorChange := True;

end;

// The firefox browser will ask for this...

if Pos('favicon', ARequestInfo.Document) > 0 then

begin

AContext.Connection.Socket.WriteFile('favicon.ico');

end;

// Post the Color change to a custom message handler

// in the main form

if DoColorChange then

PostMessage(Handle, WM_NEW_COLOR, NewColor, 0);

// Set the colors for our new HTML page

case GFormColorIndex of

0: begin

ColorValue := '#0000FF';

ColorText := 'BLUE';

end;

1: begin

ColorValue := '#FF0000';

ColorText := 'RED';

end;

2: begin

ColorValue := '#FFFF00';

ColorText := 'YELLOW';

end;

end;

// Build the HTML basically with our WEB_PAGE constant

// and call to Format

HTMLStrings := TStringList.Create;

HTMLStrings.Text := Format(WEB_PAGE, [ColorValue, ColorText]);

// Serve up the requested content

AResponseInfo.ContentText := HTMLStrings.Text;

end;

Heres the declaration for the WM_NEW_COLOR message handler

procedure WMNewColor(var AMsg: TMessage); message WM_NEW_COLOR;

Heres the custom message handler for the main form for WM_NEW_COLOR

procedure TForm1.WMNewColor(var AMsg: TMessage);

begin

case AMsg.WParam of

clBlue: rgColors.ItemIndex := 0;

clRed: rgColors.ItemIndex := 1;

clYellow: rgColors.ItemIndex := 2;

end;

end;

Heres the event handler for the radiogroupprocedure TForm1.rgColorsClick(Sender: TObject);

begin

case rgColors.ItemIndex of

0: Form1.Color := clBlue;

1: Form1.Color := clRed;

2: Form1.Color := clYellow;

end;

GFormColorIndex := rgColors.ItemIndex;

end;

STEP 4: Test your server! Start the application. Keep in mind, if you are already running a web-server on your development machine, it will have to be shut down before the application will work. Finally, fire up a web-browser, type in 127.0.0.1 and configure your server application.

Using TidQOTDServer

There are Indy servers that cover many standard protocols. Before we develop our own protocol, lets check out one more of the standard ones.

A nice feature in many software applications is the Help Quote that comes up whenever you start the software. The number of features or how extensive the documentation is for the application will have a direct effect on how many different help topics are covered. However, what if that content did not have to remain static? What if there was a way to have a special kind of server setup to deliver the Quote of the Day? With Indy, there is. The TidQOTDServer handles the standard Quote of the Day protocol. Basically this is just another example of how Indy has implemented pretty much every standard protocol. STEP 1: Create a new VCL Forms Application (Win 32). This time save it as QOTDServerExample.STEP 2: Add Controls - A TLabeledEdit to store the quote, a TButton to apply the quote (Saves it to the registry), a TMemo to log activity on the TidQOTDServer, and a checkbox that controls TidQOTDServers Active property. Figure 2 shows how it should look when it is all done.

Figure 2 The QOTD Server Application Main FormSTEP 3: Add code to the OnQOTDCommand event handler. This is the only must-have event handler for this server.Heres the code

procedure TForm1.IdQOTDServer1CommandQOTD(AContext: TIdContext;

var AQuote: string);

var

RegRdr: TRegistry;

AExcptMsg: PChar;

ALog: PChar;

begin

try

// Another way to be thread safe is to read the quote from

// the registry.

RegRdr := TRegistry.Create;

try

// Read the quote

RegRdr.OpenKey('CPWilkerson\QOTD', True);

// Assign it so that it will be sent out

AQuote := RegRdr.ReadString('TheQuote');

// DBug message that will be "posted" to the main form

ALog := StrNew(PChar('We have a quote request from '

+ AContext.Connection.Socket.Binding.IP));

PostMessage(Handle, WM_AMSG, 0, integer(ALog));

finally

RegRdr.Free;

end;

except

on E: Exception do

begin

AExcptMsg := StrNew(PChar('ERROR: TForm1.IdQOTDServer1CommandQOTD '

+ E.ClassName + ' ' + E.Message ));

PostMessage(Handle, WM_AMSG, 0, integer(AExcptMsg));

end;

end;

end;An important thing to remember here is the error handling within the event handler. Always assuming that this code could be run in a spawned thread, it is critical that exceptions be handled. If a spawned thread throws an unhandled exception, the application will crash horribly.WAIT! Before we can test this server, we need a client application!STEP 4: Create a new VCL Forms Application (Win 32). Better yet, add this project to our project group. Give it the somewhat cryptic name of QOTDClient.STEP 5: Add controls A TLabeledEdit to store the IP address of the QOTD server, a TButton to run the Quote test code, and a TidQOTD. The final look and feel of this form can be seen in Figure 3.

Figure 3 QOTD Client

STEP 6: Write the code to hook up the events of the TButton and the TidQOTD client.Here is all the code you need for the client app

procedure TForm2.btnGetQuoteClick(Sender: TObject);

var

AQuote: string;

begin

IdQOTD1.Host := edtServerIP.Text;

IdQOTD1.ConnectAndGetAll;

AQuote := IdQOTD1.Quote;

IdQOTD1.Disconnect;

ShowMessage('QUOTE: ' + AQuote);

end;

STEP 7: Test the QOTD client and server! Run both the server and client applications. Be sure not to try to connect to the server before it is running. TidTCPServer - The mother of all TCP based serversThe previous examples and all of the TCP based servers in the Indy.Sockets collection, are derived from TidTCPServer. TidTCPServer and its counterpart TidTCPClient give the developer the ability to write enterprise level applications, peer-to-peer applications, as well as the ability to support TCP based protocols that do not yet exist. Case in point, our company has designed its own protocol for having guard stations connect to central alarm monitoring station. For example, when a parking lot guard uses our software to open a gate for somebody to pass through, our software relays a TCP message to a central monitoring post that can see and log the activity.Before racing off to develop your own TCP based server, there are a few things to understand. Firstly, Socket Blocking is the way Indy operates. This means that when a server is communicating to a client it will not run any other code until it is done. This is okay in a multi-threaded environment, because every client has his own thread. That is why well designed use of Indys TCP servers scale very well. By default, all of Indys server components use multiple threads. In a high demand system, clients will not wait. In that environment, multi-threaded programs are best.

Multi-Threaded programming is not evil; however, to guide developers safely into the multi-threaded realm, Indy designers have given programmers the ability to use fibers. Fibers are NOT threads. However, fibers are more forgiving than threads when it comes to memory management because they run in a scheduled manner. This means that you do not have to worry about thread/GUI synchronizing. It is important to remember, however, that a well-designed multi-threaded application should be able to easily out-perform a fiber-based application. Again, by default Indy uses threads. To you, the developer, Indy makes this all very transparent. Simply pick the TidScheduler that suits you, and then go back to adding your event handling code. Using threads or fibers Indy components are still very fast.Do you actually need to develop a server? Remember, servers do not initiate connections. Clients initiate connections. If you are thinking of developing the next peer-to-peer file trading application. You are actually of thinking of developing an application that contains both a server and a client.

What port are you going to use for your new protocol? In a nutshell, a port is simply a number that is used to identify connections using your protocol. In theory, it can be arbitrarily picked. In the past, I have picked the street address number where the code was written. However, be careful, there are many standard protocols out there that have their own port numbers. So be sure not to use one of those. If you do, you may run into problems running your code. To get more information about port numbers, you can go to the Internet Assigned Numbers Authority or www.iana.org. Another question that needs answering is whether or not the clients will remain connected to the server. Under Windows, connections between server and client have a nasty habit of going stale. Sometimes, even Indy gets fooled by this. If the client or the server routinely (every few seconds) writes to the socket connection some kind of Heartbeat message, the connection will a) last much longer without going stale, and b) be re-constructed immediately if it does go stale.Command HandlersContinuing along the path of answering questions about deriving your own TidTCPServer is whether or not to use Command Handlers. In past versions of Indy, TidTCPServer gave the developer the ability to choose to handle an OnExecute event for all of the data on the socket or use a wonderful device called Command Handlers. Command Handlers simplify socket management by allowing the developer to simply define text based commands for the new server. Command handlers also support parameters. It really does not get any easier than this. Now that behavior has been moved to a class call TidCmdTCPServer. For our demonstration, we will use Command Handlers. However, we must not forget to set the CommandHandlersEnabled property to True when our server is instantiated.Since we have chosen to use TidCmdTCPServer we cannot simply put one on a form. We must instead descend our own class based on TidCmdTCPServer and add all of our custom behavior to it. This was even the prefered way to go even when TidTCPServer supported command handlers. The option still remains to register the new server as a Delphi component. However, before the component is thoroughly tested, it might just be easier to leave that for a later time.

Designing your protocolAt some point you will need to figure out what kind of commands your protocol needs. It is up to the developer how verbose or cryptic to make the commands. Keep in mind that a protocol that is to be implemented by the public at large should probably be somewhat simple. Whereas, a protocol that is to remain somewhat proprietary should probably be more cryptic. However, if that is the case, then perhaps using encryption is what is required. Our demonstation will be a variation of our earlier webserver application. We will create a TCP based server that allows control over the color of the server applications main form. Perhaps that is not the most useful application in the world, but I am simply trying to create an understandable demonstation here. Also, we can reuse a lot of the work we did for the HTTP server.For getting and setting the color of our main form, the protocol will have a COLOR command. It will have parameters separated by pipes (|). The first parameter is either GET or SET. If the first parameter is SET, then a second parameter is the new color. The supported colors in our demonstration are BLUE, RED, and YELLOW.Our sample protocol will maintain a continuous connection between server and client. That means we will need to implement some sort of heartbeat mechanism to insure that connection stays alive and well. In this case, the command that will be implemented is the HEARTBEAT command. It does not need to have any parameters.High Speed Command Handling Using PostMessageEarlier we spoke of how Indy uses blocking sockets. Some people have a real need to debate the pros & cons of blocking and non-blocking sockets. In laymen terms, blocking sockets means that every time a read or write is made to a socket, the code does not return until the call is complete. Non-blocking sockets return as soon as the call is made, and it is up to the application developer to look after every aspect of the socket connection. If Indy servers were single threaded mechanisms, then the idea of a blocking socket would conceivably make everyone cringe. For example, imagine a server with 100 connections and everyone wants a dynamic webpage that depends on a 10 second database stored procedure call. Hopefully all of those clients are very patient. However, Indy does not work like that. Indy servers are by default multi-threaded. I say by default because you can use fibers instead of threads. But, in the database example just mentioned, using threads would probably still be the desirable solution. That is why we need to discuss how to safely implement the command handlers of our new server.

A command handler is really just a different way of saying event handler. Only in this case the event is a class object that is being instantiated within a thread whenever one of our commands has been fired. Every TIdCommandHandler object has an OnCommand event where the code is placed to handle the command. It is very important to remember that this code is being fired within a different thread from where it is being defined. So accessing the GUI of the main application is can cause big problems. Also, absolutely every OnCommand event needs a Try..Except around its code. If something goes wrong in this code that is not handled, the server application will probably just disappear. No comforting Access Violation message boxes will appear. Just one second you will have a beautiful server application running and the nextnothing. Not even a global exception handling component will allow you to save face. In fact, any multi-threaded application that has a problem with suddenly disappearing is probably experiencing an unhandled exception in a spawned thread.Our server application requires that the client control the color of our main form. So the question needs to be asked. How do you safely affect changes to the GUI? Delphis TThread class suggests calling a Synchronize function in order to safely talk to the GUI. There is also a nice Critical Section class that allows for talking with the GUI. But, both of these solutions would require some kind of wait. Waiting is not synonymous with high-speed.

In order to be thread safe we will again make use of posting messages to the main form.STEP 1: Create a new VCL Forms Application (Win 32). This time save it as TCPServerExample. Copy the main form from the HTTP server example in order to save some time. However, in this case, you will have to strip out the HTTP component and event handler.

STEP 2: Add to this application a Win 32 unit that will hold the code for the new TCP Server. Name it MyServerU. Here is the code from the interface section of the new unit. uses

SysUtils, IdCmdTCPServer, Windows, Classes, IdCommandHandlers, Messages, IdSocketHandle, WinSock;

const

WM_NEW_COLOR = WM_USER + $7584;

WM_DBUG = WM_USER + $7585;

WM_HEARTBEAT = WM_USER + $7586;

type

TMyServer = class(TIdCmdTCPServer)

private

FMainWnd: HWND;

FColor: integer;

procedure SetMainWnd(const Value: HWND);

procedure CommandHEARTBEAT(ASender: TIdCommand);

procedure CommandCOLOR(ASender: TIdCommand);

procedure SetColor(const Value: integer);

protected

procedure InitializeCommandHandlers; override;

procedure PostDBugMsg(AString: string);

public

constructor Create(AOwner: TComponent);

property MainWnd: HWND read FMainWnd write SetMainWnd;

property Color: integer read FColor write SetColor;

end;

The MainWnd property stores the Handle value of the main form and is used with PostMessage. The Color property represents the ItemIndex that is part of the radio group object that controls the main forms color. CommandHEARTBEAT, and CommandCOLOR are the event handlers for the HEARTBEAT, and COLOR commands. The procedure InitializeCommandHandlers is a necessary override in order to set up our command handlers.Here is the code for the HEARTBEAT command handlerprocedure TMyServer.CommandHEARTBEAT(ASender: TIdCommand);

begin

try

PostMessage(FMainWnd, WM_HEARTBEAT, 0, inet_addr(PChar(ASender.Context.Connection.Socket.Binding.IP)));

except

on E: Exception do

PostDBugMsg('TMyServer.CommandHEARTBEAT '

+ E.ClassType.ClassName + ':' + E.Message);

end;

end;

Here is the code for handling the COLOR command

procedure TMyServer.CommandCOLOR(ASender: TIdCommand);

var

ColorValue: integer;

begin

ColorValue := 0;

try

PostDBugMsg('Received Color from '

+ ASender.Context.Connection.Socket.Binding.IP

+ ' Params=' + ASender.Params.Text);

if ASender.Params.Count > 0 then

begin

if UpperCase(ASender.Params[0]) = 'GET' then

begin

PostMessage(FMainWnd, WM_NEW_COLOR, FColor, 0);

end;

if UpperCase(ASender.Params[0]) = 'SET' then

begin

if UpperCase(ASender.Params[1]) = 'BLUE' then ColorValue := 0;

if UpperCase(ASender.Params[1]) = 'RED' then ColorValue := 1;

if UpperCase(ASender.Params[1]) = 'YELLOW' then ColorValue := 2;

FColor := ColorValue;

PostMessage(FMainWnd, WM_NEW_COLOR, ColorValue, 0);

end;

end;

except

on E: Exception do

PostDBugMsg('TMyServer.CommandCOLOR '

+ E.ClassType.ClassName + ':' + E.Message);

end;

end;

Client connections can be held up while a command handler is run, and therefore, we quickly run through it by not running any time consuming code in our command handlers. Notice how only the determination of the parameter values occurs here. The real work is posted to the main form. Thread safety is a must!

STEP 3: Add the event handling code to the main form.

Heres the main forms interface sectionuses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, MyServerU, IdContext, WinSock;

type

TForm1 = class(TForm)

rgColors: TRadioGroup;

Memo1: TMemo;

Label1: TLabel;

procedure rgColorsClick(Sender: TObject);

private

{ Private declarations }

FMyServer: TMyServer;

procedure WMNewColor(var AMsg: TMessage); message WM_NEW_COLOR;

procedure WMDBug(var AMsg: TMessage); message WM_DBUG;

procedure WMHeartbeat(var AMsg: TMessage); message WM_HEARTBEAT;

procedure MyServerOnConnect(AContext: TIdContext);

procedure MyServerOnDisconnect(AContext: TIdContext);

public

{ Public declarations }

procedure Initialize;

end;

var

Form1: TForm1;

As you can see by the form declaration, the form is quite light on event handling. The Initialize procedure is called just after Form1 is created in the project source. It allows you to set the MainWnd property of the TMyServer object with a valid value.Heres the code for the WMNewColor message handlerprocedure TForm1.WMNewColor(var AMsg: TMessage);

var

Cnncts: TList;

NewColor: string;

index: integer;

begin

try

rgColors.ItemIndex := AMsg.WParam;

case rgColors.ItemIndex of

0: NewColor := 'BLUE';

1: NewColor := 'RED';

2: NewColor := 'YELLOW';

end;

// Write to the client in a thread safe way

Cnncts := FMyServer.Contexts.LockList;

try

for index := 0 to Cnncts.Count - 1 do

TIdContext( Cnncts[index] ).Connection

.IOHandler.WriteLn('Current Color is ' + NewColor);

finally

FMyServer.Contexts.UnlockList;

end;

except

end;

end;

Heres the code for the WMHeartbeat message handler

procedure TForm1.WMHeartbeat(var AMsg: TMessage);

var

Cnncts: TList;

index: integer;

begin

try

Memo1.Lines.Add('Heartbeat received from '

+ inet_ntoa(in_addr(AMsg.LParam)));

// Write to the client in a thread safe way

Cnncts := FMyServer.Contexts.LockList;

try

for index := 1 to Cnncts.Count do

begin

if TIdContext( Cnncts[index] ).Connection.Socket.Binding.IP

= inet_ntoa(in_addr(AMsg.LParam)) then

TIdContext( Cnncts[index] ).Connection

.IOHandler.WriteLn('Heartbeat Received');

end;

finally

FMyServer.Contexts.UnlockList;

end;

except

end;

end;

Both of these message handlers get the Contexts object of the server, which is actually a TThreadList that represents all of the connections to the server. In order to write to a connection you need to lock the threadlist, iterate through the connections to find the one you want, and the write data to it. It is important to remember to lock and unlock the threadlist within a TryFinally block.STEP 4: If it compiles, ship it! Actually, run the application. If there is already a server running on the computer that is using the port that TMyServer wants, you will get an exception. You will either have to terminate the other server, or pick a new port number.

Client ApplicationAlthough this is not a paper discussing how to write clients for a TCP server, I will write a simple one to accommodate our new server. At the very least, we need a way to demonstrate whether or not our new server actually works.

STEP 1: Add a new VCL Forms Application (Win 32) to the project group. It needs to have a checkbox for connecting and disconnect. It will need a timer for managing heartbeats, a TidTCPClient, a radio group for select colors, a TMemo for logging activity, and a TLabel for labeling the TMemo. Running, this client application should look like Figure 4.

Figure 4 TCP Client application main form

STEP 2: Write the code.

Heres the interface section of the client applications main form

unit MainF;

interface

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,

Forms, Dialogs, IdBaseComponent, IdComponent, IdTCPConnection,

IdTCPClient, StdCtrls, ExtCtrls, IdIOHandler, IdIOHandlerSocket,

IdIOHandlerStack;

type

TForm2 = class(TForm)

CheckBox1: TCheckBox;

Memo1: TMemo;

Label1: TLabel;

Timer1: TTimer;

RadioGroup1: TRadioGroup;

IdTCPClient1: TIdTCPClient;

procedure IdTCPClient1Status(ASender: TObject; const AStatus: TIdStatus;

const AStatusText: string);

procedure IdTCPClient1Disconnected(Sender: TObject);

procedure IdTCPClient1Connected(Sender: TObject);

procedure RadioGroup1Click(Sender: TObject);

procedure Timer1Timer(Sender: TObject);

procedure CheckBox1Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form2: TForm2;

The must-have events for the TCP client component are Connected, Disconnected, and Status. Use these events primarily for keeping track of the activity between the client and server. The CheckBox1Click controls the connection and disconnection of the TCP client. The RadioGroup1Click event handler actually sends the COLOR commands to the server. The Timer1Timer event handler handles the sending of HEARTBEATs to the server.

Heres the implementation those event handlers

procedure TForm2.CheckBox1Click(Sender: TObject);

begin

if CheckBox1.Checked then

begin

IdTCPClient1.Host := '127.0.0.1';

IdTCPClient1.Connect;

IdTCPClient1.IOHandler.WriteLn('COLOR GET');

Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLnWait(3000));

end

else

begin

IdTCPClient1.Disconnect;

IdTCPClient1.IOHandler.CloseGracefully;

IdTCPClient1.IOHandler.Free;

Screen.Cursor := crHourGlass;

Timer1.Enabled := False;

Screen.Cursor := crDefault;

end;

Timer1.Enabled := CheckBox1.Checked;

end;

procedure TForm2.Timer1Timer(Sender: TObject);

var

index: integer;

begin

Timer1.Enabled := False;

try

if IdTCPClient1.Connected then

begin

IdTCPClient1.Socket.WriteLn('HEARTBEAT');

try

for index := 1 to 2 do

Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLnWait(10));

except

end;

end;

finally

Timer1.Enabled := True;

end;

end;

procedure TForm2.RadioGroup1Click(Sender: TObject);

begin

case RadioGroup1.ItemIndex of

0: IdTCPClient1.IOHandler.WriteLn('COLOR SET|BLUE');

1: IdTCPClient1.IOHandler.WriteLn('COLOR SET|RED');

2: IdTCPClient1.IOHandler.WriteLn('COLOR SET|YELLOW');

end;

//Memo1.Lines.Add(IdTCPClient1.IOHandler.ReadLnWait(3000));

end;

procedure TForm2.IdTCPClient1Connected(Sender: TObject);

begin

Memo1.Lines.Add('Connected to back room server.');

end;

procedure TForm2.IdTCPClient1Disconnected(Sender: TObject);

begin

Memo1.Lines.Add('Disconnected from back room server.');

end;

procedure TForm2.IdTCPClient1Status(ASender: TObject; const AStatus: TIdStatus;

const AStatusText: string);

begin

Memo1.Lines.Add('Status: ' + AStatusText);

end;

Congratulations! If everything worked out you should be able to run both the server and the client applications and see the client control the server much like the HTTP server demonstration. Conclusion

I have barely scratched the surface of developing servers with Indys TCP based servers. However, with these examples you can begin to see how easy it is to use these components. Whether it is developing a server for a standard protocol, or developing a server for some custom protocol Indy is a pretty solid way to go. The product is free, there is lots of support in the newsgroups, it comes with the source code, and best of all it works. The team that put this product together continues to improve it and their latest and greatest can be found at www.indyproject.org. They also offer paid support if you need a question answered yesterday, but to their credit, in all of the years I have used this product, I have never required that support (Of course I still feel obligated to buy them a beer or something).