Friday, March 27, 2009

Introduction to Indy

 

Source : http://conferences.codegear.com/article/32159

 

By: Chad Hower

Abstract: Learn the basics of Indy and how to use it in the Microsoft .NET Framework and Win32.�

Chad Hower works for Atozed Software, and is the original author of Indy and IntraWeb. When not programming, he likes to bike, kayak, ski, drive, or just be outdoors. Chad is an ex-patriate who spends his summers in St. Petersburg, Russia, winters in Cyprus, and travels extensively year round.

cpub@hower.org

Introduction to Indy

 

The Indy Way

 

Indy is designed from the ground up to be threadable. Building servers and clients in Indy is similar to the way Unix servers and clients are built, except that it is much easier, because you have Indy and Delphi. Unix applications typically call the stack directly with little or no abstraction layer.

 

Typically Unix servers have one or more listener processes which look for incoming client requests. For each client that it needs to serve, it will fork a new process to handle each client. This make programming very easy as each process deals with only one client. The process also runs in its own security context, which can be set by the listener or the process based on credentials, authentication, or other means.

 

Indy servers work in a very similar manner. Windows unlike Unix does not fork well, but it does thread well. Indy servers allocate a thread for each client connection.

 

Indy servers set up a listening thread that is separate from the main thread of the program. The listener thread listens for incoming client requests. For each client that it answers, it spawns a new thread to service that client. The appropriate events are then fired within the context of that thread.

 

 

Indy Methodology

 

Indy is different from other socket components with which you may be familiar. If you have never worked with other socket components, you will find Indy very easy as Indy operates in a fashion that you expect. If you have worked with other socket components, please forget what you know. It will only hinder you and cause you to make false assumptions.

 

Nearly all other components use non-blocking calls and act asynchronously. They require you to respond to events, set up state machines, and often perform wait loops.

 

For example, with other components, when you call connect you must wait for a connect event to fire, or loop until a property indicates that you are connected. With Indy, you merely call Connect, and wait for it to return. If it succeeds, it will return when it has completed its task. If it fails, it will raise an exception.

 

Working with Indy is very much like working with files. Indy allows you to put all your code in one place, instead of scattered throughout different events. In addition, most find that Indy is much easier to use. Indy is also designed to work well with threads. If at anytime you have trouble implementing something with Indy, step back and approach it like a file.

 

 

 

How Indy is Different

 

7    Indy uses the blocking socket API, or IOCP when using Super Core.

7    Indy does not rely on events. Indy has events that can be used for informational purposes, but are not required.

7    Indy is designed for threads. Indy however can be used without threads.

7    Indy is programmed using sequential programming.

7    Indy has a high level of abstraction. Most socket components do not effectively isolate the programmer from stack. Most socket components instead of isolating the user from the complexities of stack merely pass them on with a basic class wrapper.

 

Overview of Clients

 

Indy is designed to provide a very high level of abstraction. Intricacies and details of the TCP/IP stack are hidden from the Indy programmer.

 

A typical Indy client session looks like this:

 

Delphi

with TIdTCPClient.Create do try

Host := 'news.atozedsoftware.com';

Port := 119;

Connect; try

// Read and write here

finally Disconnect; end;

finally Free; end;

 

C#

using (TCPClient LClient = new TCPClient()) {

LClient.Host = "news.atozedsoftware.com";

LClient.Port = 119;

LClient.Connect();

try {

// Read and write here

}

finally {

LClient.Disconnect();

}

}

 

 

 

Overview of Servers

Indy server components create a listener thread that is separate from the main thread of the program. The listener thread listens for incoming client requests. For each client that it answers, it then spawns a new thread to service that client. The appropriate events are then fired within the context of that thread.

 

Threading

 

Threading is the process of using threads to implement functionality. Indy uses threads extensively in its server implementations and threading is useful for clients as well.

 

 

 

TCP

 

TCP (Transmission Control Protocol) is a stream based protocol. TCP connections require a connection before data can be transmitted. When the session is complete, an explicit disconnect should occur. TCP provides for error checking, retry, and order of delivery. Thus TCP is a reliable protocol and used for most communications.

 

TCP Clients

 

Basic Client

 

A basic Indy client takes this form:

 

Delphi

with TIdTCPClient.Create do try

Host := 'news.atozedsoftware.com';

Port := 119;

Connect; try

// Read and write here

finally Disconnect; end;

finally Free; end;

 

C#

using (TCPClient LClient = new TCPClient()) {

LClient.Host = "news.atozedsoftware.com";

LClient.Port = 119;

LClient.Connect();

try {

// Read and write here

}

finally {

LClient.Disconnect();

}

}

 

In Delphi the host and port can also be set at design time using the object inspector. This is the minimum code required to write a client using Indy. The minimum requirements for creating a client are as follows:

 

1. Set Host property.

2. Set Port property. This is only required if there is no default port. Most protocols provide a default port.

3. Connect.

4. Transfer Data. This includes reading and writing.

5. Disconnect.

 

 

Handling Exceptions

 

Handling exceptions with Indy clients is the same as it is with files. If an error occurs during a call to any Indy method, the appropriate exception will be thrown. To handle these exceptions, code should be properly wrapped with try blocks.

 

There are no OnError events, so do not go looking for them. This might seem strange if you have used other socket libraries, but consider file stream. File streams do not have OnError events, it simply throws exceptions if there is a problem. Indy works in exactly the same manner.

 

Just as all file opens should be matched with a file close, all Connect calls in Indy should be matched with a call to Disconnect. Basic Indy clients should start out in this fashion:

 

Delphi

LClient.Connect; try

// Perform read/write here

finally LClient.Disconnect; end;

 

C#

LClient.Connect();

try

{

// Read and write here

}

finally

{

LClient.Disconnect();

}

 

Indy exceptions are easy to differentiate from other exceptions because all Indy exceptions descend from EIdException. If you wish to handle Indy errors separately from other exceptions, it can be done as in the following example.

 

Delphi Note: To use EIdException you will need to add IdException to your uses clause.

 

Delphi

try

Client.Connect; try

// Perform read/write here

finally Client.Disconnect; end;

except

on E: EIdException do begin

// Handle Indy exception here

end;

end;

 

C#

try {

LClient.Connect();

try {

// Read and write here

}

finally {

LClient.Disconnect();

}

}

catch (EIdException e) {

// Handle Indy exception here

}

 

If there is an error during the call to the Connect method, it will clean itself up before throwing the appropriate exception. Because of this, the try..finally is after the call to Connect and not before. However, if there is an exception during your data transfer only an exception will be raised. The socket will remain connected. It is your responsibility to retry your operation or disconnect. In this example, no extra handling is performed and the socket is disconnected upon any error, and upon normal completion.

 

 

Exceptions are not Errors

 

Many developers have been taught or have assumed that exceptions are errors. This is not the case. If this were the case they would be called errors instead of exceptions. An exception is merely something that is out of the ordinary. In terms of software, exceptions are something that occur and alter the normal program flow.

 

Exceptions are used to communicate error conditions, and most exceptions are errors. Indy also defines several exceptions that are not errors. Such exceptions typically descend from EIdSilentException and thus can easily be distinguished from errors and other exceptions.

 

 

Delphi Debugger Note

 

Using exceptions for non errors is not unique to Indy. The VCL uses non error exceptions internally as well. One example of this is EAbort. Delphi is configured to ignore EAbort when debugging though so most users never see this. The silent exceptions thrown by Indy are caught by the debugger and this leads many users to believe the are errors. Simply press F9 to continue execution of your program. These exceptions will not be seen when run from an executable file outside the debugger as they are caught by Indy. Uncaught exceptions stop twice in the debugger, once for the debugger and once for the default exception mechanism built into each program.

 

The debugger can be told to ignore Indy's silent exceptions just as it does for EAbort. Unless you are debugging behaviour related to these exceptions, you should configure the debugger to ignore them. To do this, select Debugger Options from the Tools menu and select the Language Exceptions tab. The dialog should appear as show below.

 

In later versions of Delphi, Borland did add EIdConnClosedGracefully to the list of default ignore exceptions. EIdConnClosedGracefully is the most common of the silent exceptions and therefore will catch most occurrences. However to catch all of them EIdSilentException should be explicitly added. To do this click the Add button. A dialog as show below will appear.

 

Enter EIdSilentException in the Exception Type field and click OK. The Language Exceptions tab should now list EIdSilentException as well.

 

 

 

TIdAntiFreeze

 

Indy has a special component that solves the user interface freeze problem transparently. The existence of a single TIdAntiFreeze instance in an application, allows the use of blocking calls in the main thread without the User Interface being frozen.

 

The TIdAntiFreeze works by internally timing out calls to the stack and allowing messages to be processed during the timeout periods. The external calls to Indy continue to block, and thus code works exactly as without a TIdAntiFreeze.

 

Since the user interface freeze is only affected by blocking calls in the main thread, TIdAntiFreeze only affects Indy calls made from the main thread. If an application uses Indy in threads, TIdAntiFreeze is not required. If used, it will only affect calls made from the main thread.

 

Use of a TIdAntiFreeze will slow the socket communications somewhat. How much priority should be given to the application versus the socket communication is configurable with the properties of TIdAntiFreeze. The reason that usage of a TIdAntiFreeze slows the socket communication is that the main thread is allowed to process messages. Because of this care must be taken to not allow too much time to be consumed in events caused by messages. These include most user interface events such as OnClick, OnPaint, OnResize and many others. Since non-blocking sockets rely on messages themselves, the described problem always applies to non-blocking sockets. With Indy and the optional use of a TIdAntiFreeze, the programmer has complete control.

 

 

EIdConnClosedGracefully

 

Many Indy users are annoyed by the EIdConnClosedGracefully exception that is raised with Indy servers, especially the HTTP and other servers. EIdConnClosedGracefully is an exception signaling that the connection has been closed by the other side intentionally. This is not the same as a broken connection which would cause a connection reset error. If the other side has closed the connection and the socket is read or written to, EIdConnClosedGracefully will be raised by Indy. This is similar to attempting to read or write to a file that has been closed without your knowledge.

 

In some cases this is a true exception and your code needs to handle it. In other cases (typically servers) this is a normal part of the functioning of the protocol and Indy handles this exception for you. Even though Indy catches it, when running in the IDE the debugger will be triggered first. You can simply press F9 to continue and Indy will handle the exception, but the constant stopping during debugging can be quite annoying. In the cases where Indy catches the exception, your users will never see an exception in your program unless it is run from the IDE.

 

 

Why Does This Exception Occur in Servers?

 

When a client is connected to a server there are two common ways to handle the disconnection:

 

1. Mutual Agreement - Both sides agree to mutually disconnect by one side signaling (and the other optionally acknowledging) and then both sides disconnecting explicitly.

2. Single Disconnect - Disconnect and let the remote side take notice.

 

With the Mutual Agreement method both sides know when to disconnect and both explicitly disconnect. Most conversational protocols such as Mail, News, etc disconnect in this manner. When the client is ready to disconnect it sends a command to the server telling it that it will disconnect. The server replies with an acknowledgement of the disconnect request, and then both the client and server disconnect. In these cases an EIdConnClosedGracefully should not be raised, and if one occurs it is in fact an error and should be handled.

 

In some cases of Mutual Disconnect no command will be issued, but both sides know when the other will disconnect. Often a client will connect, issue one command, receive the response from the server and disconnect. While no explicit command was issued by the client, the protocol states that the connection should be disconnected after one command and response. Some of the time protocols are examples of this.

 

With Single Disconnect, one side just disconnects. The other side is left to detect this and then take appropriate action to terminate the session. With protocols that use this disconnection method you will see EIdConnClosedGracefully and it is normal. It is an exception, but Indy knows about it and will handle it for you. The whois protocol is an example of this. The client connects to the server and sends a string containing the domain to query. The server then sends the response and disconnects when the response is finished. No other signal is sent to the client other than a normal disconnection that the response is finished.

 

The HTTP allows for both Mutual Agreement and Single Disconnect and this is why it is common to see the EIdConnClosedGracefully with the HTTP server. HTTP 1.0 works similar to the whois protocol in that the server signals the client simply by disconnecting after the request has been served. The client then must use new connections for each request.

 

HTTP 1.1 allows a single connection to request multiple documents. However there is no command to signal a disconnect. At any time, either the client or the server can disconnect after a response. When the client disconnects but the server is still accepting requests, a EIdConnClosedGracefully will be raised. In most cases in HTTP 1.1, the client is the one that disconnects. A server will disconnect when it implements part of HTTP 1.1, but does not support the keep alive option.

 

 

 

Why is it an Exception?

 

Many users have commented that maybe it there should be a return value to signal this condition instead of an exception. However this is the wrong approach in this case.

 

The EIdConnClosedGracefully is raised from a core routine, however when this routine is called it is normally several method calls deep. The EIdConnClosedGracefully is in fact an exception and needs to be trapped by the topmost caller in most cases. The proper way to handle this is an exception.

 

 

Is it an Error?

 

All exceptions are not errors. Many developers have been taught or assumed that all exceptions are errors. However this is not the case, and this is why they are called exceptions and not errors.

 

Exceptions are exactly that - exceptions. Exceptions to handle errors in an elegant way. However exceptions have other uses besides errors as well. In the VCL, EAbort is one example of an exception that is not necessarily an error. Exceptions such as these are used to modify standard program flow and communicate information to a higher calling level where they are trapped. Indy uses exceptions in such a way as well.

 

 

 

When is it an Error?

 

When EIdConnClosedGracefully is raised in a client, it is an error and you should trap and handle this exception.

 

In servers it is an exception. However sometimes it is an error, and sometimes it is an exception. For many protocols this exception is part of the normal functioning of the protocol. Because of this common behavior, if you do not catch the EIdConnClosedGracefully in your server code, Indy will. It will then mark the connection as closed and stop the thread assigned to the connection. If you wish to handle this exception yourself you may, otherwise Indy will handle it and take the appropriate actions for you automatically.

 

 

Simple Solution

 

Because the EIdConnClosedGracefully is a common exception especially with certain servers it descends from EIdSilentException. On the Language Exceptions tab of Debugger Options (Tools Menu) you can add EIdSilentException to the list of exceptions to ignore. After this is added the exceptions will still occur in the code and be handled, but the debugger will not stop the program to debug them.

 

TCP Servers

 

Indy has a variety of server models depending on your needs and the protocol used. This section provides an overview of the base Indy TCP server components.

 

 

Server Types

 

TIdTCPServer

 

The most prominent Indy server is TIdTCPServer. In default configuration, TIdTCPServer functions as described next.

 

TIdTCPServer creates a secondary listener thread that is independent from the main thread of the program. The listener thread listens for incoming requests from clients. For each client that it answers, it creates a new thread to specifically service that individual client connection. The appropriate events are then fired within the context of that thread.

 

 

The Role of Threads
 

Indy servers are designed around threads and operate in a manner similar to how Unix servers operate. Unix applications typically interface to the stack directly with little or no abstraction layer. Indy isolates the programmer from the stack using a high level of abstraction and internally implements many details that can be automatic and transparent.

 

Typically, Unix servers have one or more listener processes that watch for incoming requests from clients. For each client request that the listener process accepts, the server forks a new process to handle each client connection. Handling multiple client connections in this manner is very easy as each process deals with only one client. The process also runs in its own security context, which can be set by the listener or the process itself, based on credentials, authentication, or other means.

 

Indy servers operate in a very similar fashion. Windows unlike Unix does not fork well, but Windows does perform threading quite well. Indy servers allocate a thread for each client connection instead of a complete process as Unix does. This provides for nearly all the advantages of processes, with none of the disadvantages

 

 

Servers on Fibers
 

Indy 10 has an option to use fibers instead of threads to serve client connections. This model differs from the default model in that fibers replace the individual threads. This model is covered separately.

 

 

TIdUDPServer

 

Since UDP is connectionless, it operates differently than TIdTCPServer. It does not have any modes similar to TIdSimpleServer, but since UDP is connectionless, it does have single use listening methods.

 

When active it creates a listening thread to listen for inbound UDP packets. For each UDP packet received, it fires the OnUDPRead event.

 

When ThreadedEvent is false, the OnUDPRead event will be fired in the context of the main program thread. When ThreadedEvent is true, the OnUDPRead event is fired in the context of the listener thread. TIdUDPServer does not spawn peer threads for each connection regardless of the setting of ThreadedEvent.

 

When ThreadedEvent is true or false, its execution will block the receiving of more messages. Because of this the processing of the OnUDPRead event should be quick, or should spawn off additional threads to handle lengthy tasks.

 

 

 

TIdSimpleServer

 

TIdSimpleServer is for creating single use servers. It is intended to service a single connection at a time. While it can service another request after it is finished with a request, it is typically used for single use requests.

 

It will not spawn any listener or secondary connection threads. All of its functionality occurs from within the thread that it is used.

 

TIdFTP client component utilizes TIdSimpleServer. When FTP performs a file transfer, a secondary TCP connection is opened to transfer the data, and closed when the data has been transferred. This secondary connection is called the data channel and is unique for each file transfer.

 

 

 

Threaded Events

 

TIdTCPServer events are threaded. This means that while they are not part of a thread class, they are executed from with in a thread. This is a very important detail. Please be sure to understand this detail before proceeding. If you are not familiar with threads, please see the threading section.

 

This might seem a bit confusing at first how the event can appear as part of the form, yet execute from within a thread. However it was constructed this way intentionally so that events could be created at design time just like any other event, without the need to create custom classes and override methods manually.

 

If descendant components are being built, methods can be overriden instead. However for building applications, the event based model is much easier to use.

 

Events Called From Threads

Each client is assigned its own thread. Using that thread the events of the TCP server (which are parts of the form or data module when created) are called from those threads. This means that a single event may be called multiple times from several threads.

 

Examples of threaded events are OnConnect, OnExecute, and OnDisconnect.

 

 

TCP Server Models

 

Indy's TCP server supports two models for implementing servers. These methods are OnExecute, and command handlers.

 

 

OnExecute

 

OnExecute refers to the OnExecute event of TIdTCPServer. When implementing a server using this model, the OnExecute must be defined, or the DoExecute method must be overridden.

 

Some protocols are binary or have no command structure and are not suited for command handlers. For such servers the OnExecute event should be used. OnExecute occurs repeatedly as long as the connection is alive and passes the connection as its argument. Using the OnExecute model allows complete control by the developer and allows implementation of any type of protocol including binary protocols.

 

After a client connects to a server the OnExecute will be fired. If no OnExecute is defined, an exception will be raised. The OnExecute event is fired from inside of a loop as long as the client is connected. This is a very important detail, and because of this developers must be cautious to:

 

1. Remeber the event is inside a loop.

2. Not implement looping that will interfere with Indy.

 

The loop is constructed internally in Indy as shown in this diagram:

 

TCP Server Event Flow

 

The Check Connection step performs the following checks:

 

7    Client is still connected

7    Disconnect has not been called during OnExecute

7    There were no fatal errors

7    There were no unhandled exceptions in OnExecute

7    Server is still active

 

If all of these conditions and other checks are true, OnExecute is called again.

 

 

Example
 

A very simple server implemented using OnExecute might look like this:

 

Delphi

procedure TformMain.IdTCPServer1Execute(AContext: TIdContext);

var

LCmd: string;

begin

with AContext.Connection.IOHandler do begin

LCmd := Trim(ReadLn);

if AnsiSameText(LCmd, 'QUIT') then begin

WriteLn('200 Good bye');

AContext.Connection.Disconnect;

end else if AnsiSameText(LCmd, 'DATE') then begin

WriteLn('200 ' + DateToStr(Date));

end else begin

WriteLn('400 Unknown command');

end;

end;

end;

 

C#

private void TCPServerExecute(Context AContext) {

Indy.Sockets.IndyIOHandler.IOStream LIOH = AContext.Connection.IOHandler;

string LLine = LIOH.ReadLn().ToUpper();

 

if (LLine == "QUIT") {

LIOH.WriteLn("200 Good bye");

AContext.Connection.Disconnect();

}

else if (LCmd == "DATE") {

LIOH.WriteLn("200 " + DateTime.Today.ToShortDateString());

}

}

 

Remember, there is no need to check for a valid connection because Indy does that automatically. There also is no need to perform any looping as Indy will also do that for you. Indy calls the event repeatedly until there is no longer a connection. This can be caused either by an explicit disconnect a shown above, by a network error, or by the client disconnecting. In fact, no looping should be done as it might interfere with Indy's disconnect detection. If looping must be done internally to this event special care must be take to allow such exceptions to bubble up to Indy so that it can handle them as well.

 

 

Command Handlers

 

Command handlers make building servers much easier, however they are not the best approach for all situations. Command handlers are limited to conversational text protocols. The data portions of the protocols can however contain binary data. Most protocols are text based however so command handlers can be used.

 

Command handlers are a concept used in TIdTCPServer that allow the server to do command parsing and processing for you. Command handlers are like action lists for building servers in a RAD fashion using a design time environment and are just a very small sneak peak into the future of Indy servers.

 

For each command that you want the server to handle, a command handler is created. Think of command handlers as action lists for servers. The command handler contains properties that tell it how to parse the command including how to parse parameters, the command itself, some actions that it can possibly perform itself, and optional auto replies. In some cases using the properties alone, you can create a fully functional command without having to write any code. Each command handler has a unique OnCommand event. When the event is called there is no need to determine which command has been requested as each event is unique to a command handler. In addition, the command handler has taken optional actions for you already, and parsed the parameters for your use.

 

 

 

 

Here is a very basic demo of how to use command handlers. First we must define the two commands: QUIT and DATE. To do this two command handlers are created at design time as shown here:

 

For cmdhQuit the disconnect property is set to true. For cmdhDate the OnCommand event is defined as shown here:

 

Delphi

procedure TForm1.IdTCPServer1cmdhDateCommand(ASender: TIdCommand);

begin

ASender.Reply.Text.Text := DateTimeToStr(Date);

end;

 

C#

private void TCPServerDateCommand(Command ASender) {

ASender.Reply.Text.Text = DateTime.Today.ToShortDateString();

}

 

Yes, it really is ASender.Reply.Text.Text and not simply ASender.Reply.Text. ASender.Reply.Text.Text its of type TStrings. You can use Add, Insert, etc. But in this case its own Text property was most appropriate.

 

This is the complete code for the command handler version. All other details have been specified by setting properties of the command handlers.

 

 

Command Handlers

 

Creating servers in Indy has always been fairly straightforward, however with Indy 9 it has become even easier with the introduction of command handlers to Indy's TCP server (TIdTCPServer).

 

Command handlers are in a fashion, action lists for servers. Command handlers work in such a fashion that you define a command handler for each command, and then using that command handler define the behavior and responses for that particular command. When a command is received from the client, the server automatically parses it and passes it to the appropriate command handler. Command handlers not only have properties to customize their behavior, but have methods and events as well.

 

Command handlers only work with text based command and response (conversational) TCP protocols. However this covers about 95% of the servers that are in common use today. While command handlers can deal with binary data, they can only deal with text commands. There are some protocols which use binary commands. For protocols using binary commands, or text commands that are not conversational, the implementation of Command Handlers also preserves backwards compatibility and allows servers to be implemented without them.

 

 

Implementation

 

TCPServer contains a property named CommandHandlers which is a collection of command handlers. Command handlers are usually created at design time, however for protocol implementing descendants they can also be created at run time. If command handlers are created at run time they should be created by overriding the InitializeCommandHandlers method. This will ensure that they are only created a run time. If they are created in the constructor they will be created each time the TCPServer is streamed in, and when it is streamed out they will be saved. Soon there will be many copies of each command handler. Initialize is called after the TCPServer is activated for the first time.

 

TCPServer contains several properties and events relating to command handlers. CommandHandlersEnabled enables or disables command handler processing as a whole. OnAfterCommandHandler is fired after each command handler is processed and OnBeforeCommand handler is fired before each command handler is processed. OnNoCommandHandler is fired if no command handler is found that matches the command.

 

If CommandHandlersEnabled is true and command handlers exists, command handler processing is performed. Otherwise the OnExecute event is called if assignd. OnExecute is not called if command handler processing is performed.

 

As long as the connection exists, TCPServer will read lines of text from the connection and attempt to match command handlers to the data. Any blank lines will be ignored. For non blank lines first OnBeforeCommandHandler will be fired. Next a matching command handler will be searched for. If a matching command handler is found and its enabled property is true, its OnCommand event will be fired. Otherwise OnNoCommandHandler is fired. Finally, OnAfterCommand handler will be fired.

 

 

Example Protocol

 

To demonstrate a basic implementation of command handlers a simple protocol will be defined. For the sake of demonstration a custom time server will be implemented consisting of three commands:

 

7    Help - Display a list of supported commands and basic help on each.

7    DateTime <format> - Return the current date and/or time using the specified format. If no format is specified the format yyyy-mm-dd hh:nn:ss will be used.

7    Quit - Terminate the session and disconnect.

 

This is a very basic implementation, but will work quite nicely for demonstration purposes. You may wish to expand on it to further play with the capabilities of command handlers.

 

 

Base Demo

 

First lets construct the base of the demo which will then be built upon. It is assumed that you are already familiar with TIdTCPServer and what the following steps do. To build the base of the demo perform the following steps:

 

1. Create a new application.

2. Add a TIdTCPServer to the form.

3. Set the Default property to 6000. 6000 is just an arbitrary number and any free port can be used.

4. Set the Active property to True. This will activate the server when the application is run.

 

This will create the base application. It does not do anything yet though as no events or command handlers have been created.

 

Creating a Command Handler

 

Command handlers are defined by editing the CommandHandlers property of TIdTCPServer. The CommandHandlers property is a collection. Command handlers can be modified at run time and/or design time. To edit command handlers at design time select the ellipsis button of the CommandHandlers property in the Object Inspector. A dialog like this should appear:

 

 

It is empty because no command handlers have been created. To create a command handler either right click on the white area and select add, or select the first button on the toolbar. After doing this a command handler will be listed in the property editor as shown here:

 

To edit the command handler this property editor is used to select the command handler, and then the object inspector is used to edit its properties and events. Editing command handlers is the same as editing fields of a dataset or columns of a DBGrid. If the object inspector is not visible, press the F11 key to display it.

 

The object inspector will appear similar to this one. This one has some properties modified already from the defaults for implementing the QUIT command and will be covered next.

 

Step by step here are the properties that have been modified to implement the QUIT command:

 

1. Command = Quit - This is the command that the server use to match input from the client and determine which command handler will handle the command. Command is case insensitive.

2. Disconnect = True - This tells the server to disconnect the client after this command has been processed.

3. Name = cmdhQuit - This has no effect on the behavior of the command handler, but makes it easier to identify in code if necessary. This step is optional.

4. ReplyNormal.NumericCode = 200 - Commands are typically replied to with a 3 digit numeric code and optional text. This tells the command handler to reply with 200 plus any text found in ReplyNormal.Text if no errors occur during processing of the command.

5. ReplyNormal.Text = Good Bye - This text is also sent with ReplyNormal.NumericCode.

 

A fully functional command handler has now been created.

 

Command Handler Support

 

An initial command handler has been created, however there are several global options relating to text based servers and command handlers that should be set as well. All of the properties discussed here are properties of the TIdTCPServer itself and not individual command handlers.

 

 

Greeting
 

It is common practice for servers to provide a response upon connection before the server receives commands from the client. A typical reply that indicates the server is ready is 200, and setting it to non zero will enable sending of the greeting.

 

Set Greeting.NumericCode = 200 and Greeting.Text to "Hello".

 

 

ReplyExceptionCode
 

If any unhandled exceptions occur during the processing of a command this number will be used to construct a reply to the client if its value is non zero. 500 is a typical reply for internal unknown errors. For the text portion of the reply the exception text will be sent.

 

Set ReplyExceptionCode to 500.

 

 

ReplyUnknown
 

If no matching command handler is found for a command issued by a client, TIdTCPServer's ReplyUnknownCommand property is used to return an error to the client. 400 is a common reply for internal errors.

 

Set ReplyUnknown.NumericCode to 400 and ReplyUnknown.Text to "Unknown Command".

 

Other Properties
 

There are other properties and even events of TIdTCPServer for implementing additional behaviors related to command handlers, but the ones listed here are the ones that should be implemented as a minimum.

 

 

Testing the New Command

 

Now that an initial command has been created it can be tested easily using telnet since command handlers are text based. To test the new command:

 

1. Run the application.

2. From the Start : Run dialog enter: telnet 127.0.0.1 6000 and press OK. This instructs telnet to connect to the computer on port 6000 which is the port that the demo is listening on.

3. The server should reply with 200 Hello which is the greeting that was defined in the Greeting property of TIdTCPServer earlier.

4. Telnet will then display a caret. This means that the server is ready and waiting for input (i.e. a command).

5. Type HELP and press enter. The server responds with "400 Unknown Command". This is because no HELP command has been implemented yet, and the "400 Unknown Command" was what was defined in the ReplyUnknown property.

6. Type QUIT. The server responds with "200 Good Bye" and disconnects the client.

 

Congratulations! You have just built a server using command handlers. The next section will progress with implementing the other two command HELP and DATETIME which have different behaviors and needs from QUIT.

 

 

Implementing HELP

 

The HELP command is similar in behavior to the QUIT command with these two differences.

 

1. It does not disconnect.

2. In addition to the status reply, it also provides a textual response with the help information.

 

To implement the HELP command perform the following steps:

 

1. Create a new command handler.

2. Command = Help

4. Name = cmdhHelp

5. ReplyNormal.NumericCode = 200

6. ReplyNormal.Text = Help Follows

 

All of these steps you should be familiar with as they are similar to those implemented in QUIT. The additional property that is used for implementing the textual form of the response is the Response property which is a string list. If Response contains text, it will be sent to the client after ReplyNormal is sent. For implementation of the HELP command use the string list property editor for the Response property and enter:

 

Help - Display a list of supported commands and basic help on each.

DateTime <format> - Return the current date and/or time using the specified format.

If no format is specified the format yyyy-mm-dd hh:nn:ss will be used.

Quit - Terminate the session and disconnect.

 

Now if you connect to the server and send the HELP command the server will reply as follows:

 

200 Hello

help

200 Help Follows

Help - Display a list of supported commands and basic help on each.

DateTime <format> - Return the current date and/or time using the specified format.

If no format is specified the format yyyy-mm-dd hh:nn:ss will be used.

Quit - Terminate the session and disconnect.

.

 

 

 

Implementing DATETIME

 

DATETIME is the final command in the implementation of this protocol. It differs from either QUIT or HELP in that it requires some custom functionality that cannot be created merely by using properties. In the implementation of DATETIME an event will be used to implement this custom behavior.

 

First build the base command handler using steps you are already familiar with:

 

1. Create a new command handler.

2. Command = DateTime

3. Name = cmdhDateTime

4. ReplyNormal.NumericCode = 200

 

This time a ReplyNormal.Text was not defined, the event will custom defined it for each request. To define the event, use the Object Inspector while the DATETIME command handler is selected. Switch to the events tab and create an OnCommand event. Delphi will create a event shell as shown next:

 

procedure TForm1.IdTCPServer1TIdCommandHandler2Command(ASender: TIdCommand);

begin

 

end;

 

OnCommand passes in an argument of ASender which is of type TIdCommand. This is not the command handler, but the command itself. Command Handlers are global to all connections, while commands are specific to the connection being handled by this instance of the OnCommand event. This ensures that the event can provide specific behavior to each client connection.

 

Before the event is called, Indy will create an instance of the command and initialize its properties based on the command handler. You can then use the command to change properties from their defaults, call methods to instruct the command to perform tasks, or access its Connection property to interact with the connection directly.

 

This protocol defines DATETIME as accepting an optional parameter specifying the format of the date and time to be returned. The command has support for this as well in the Params property, which is a string list. When a command is received from the client, if the command handler's ParseParams property is True (True is the default) Indy will use the CmdDelimeter property (which defaults to #32 or space) to parse the command into the command and its parameters.

 

For example in this protocol the client may send:

 

DATETIME hhnnss

 

In this case, ASender.Params would contain the string "hhnnss" in ASender.Params[0]. The number of parameters can be determined by reading ASender.Params.Count.

 

Using these properties the OnCommand can be implemented as follows:

 

procedure TForm1.IdTCPServer1TIdCommandHandler2Command(ASender: TIdCommand);

var

LFormat: string;

begin

if ASender.Params.Count = 0 then begin

LFormat := 'yyyy-mm-dd hh:nn:ss';

end else begin

LFormat := ASender.Params[0];

end;

ASender.Reply.Text.Text := FormatDateTime(LFormat, Now);

end;

 

This implementation merely reads the parameters and uses ASender.Reply.Text to send the reply back to the client. It is not needed to set ASender.Reply.NumericCode as Indy initializes it to 200 from the command handler's ReplyNormal.NumericCode.

 

Note the use of ASender.Reply.Text.Text. Text is required twice because the Text property of the command is a string list, and we are accessing TStrings.Text in addition to that. Since it is a string list, other methods or properties such as Add, Delete, etc may also be used. Text is used here as ASender.Reply.Text may be pre-initialized in some cases and using ASender.Reply.Text.Text will overwrite any preexisting text.

 

If the demo is tested again using telnet, it will yield results similar to the following:

 

200 Hello

datetime

200 2002-08-26 18:48:06

 

In some cases Params cannot be used. DATETIME is one of them. Consider if the user sends this as a command:

 

DATETIME mm dd yy

 

In this case Params.Count would be 3, and the event would fail and return only the value for months (mm). For cases where the parameter has embedded delimiters the UnparsedParams property of the command should be used instead. Optionally the ParseParams property can be set to False. UnparsedParams will contain the data irregardless of the value of ParseParams, but setting it to false will increase efficiency by telling Indy that there is no need to parse the parameters into the Params property.

 

The event with the code modified to use UnparsedParams follows:

 

procedure TForm1.IdTCPServer1TIdCommandHandler2Command(ASender: TIdCommand);

var

LFormat: string;

begin

if ASender.Params.Count = 0 then begin

LFormat := 'yyyy-mm-dd hh:nn:ss';

end else begin

LFormat := ASender.UnparsedParams;

end;

ASender.Reply.Text.Text := FormatDateTime(LFormat, Now);

end;

 

 

 

Conclusion

 

Command handlers are very flexible and contain many more properties and methods than covered here. This is only an introduction to command handlers and their capabilities. Hopefully enough has been covered to pique your interest and get you started.

 

There are also special plans for future versions of Indy to make command handlers even more visual in the design phase.

 

 

UDP

 

UDP (User Datagram Protocol) is for datagrams and is connectionless. UDP allows lightweight packets to be sent to a host without having to first connect to another host. UDP packets are not guaranteed to arrive at their destination, and may not arrive in the same order they were sent. When sending a UDP packet, it is sent in one block. Therefore, you must not exceed the maximum packet size specified by your TCP/IP stack.

 

Because of these factors, many people assume UDP is nearly useless. This is not the case. Many streaming protocols, such as Real Audio, use UDP.

 

 

Note

 

The term "streaming" can be easily confused with "stream" connection, which is TCP. When you see these terms, you need to determine the context in which they are used to determine their proper meaning.

 

 

Reliability

 

The reliability of UDP packets depends on the reliability and the saturation of the network. UDP packets are often used on applications that run on a LAN, as the LAN is very reliable. UDP packets across the Internet are generally reliable as well and can be used with error correction or more often interpolation. Interpolation is when an educated guess is made about missing data based on packets received before and / or after. Delivery however cannot be guaranteed on any network - so do not assume your data will always arrive at your destination.

 

Because UDP does not have delivery confirmation, its not guaranteed to arrive. If you send a UDP packet to another host, you have no way of knowing if it actually arrived at its destination. The stack will not - and cannot - determine this, and thus will not provide an error if the packet did not reach its destination. If you need this information, you need to send some sort of return notification back from the remote host.

 

UDP is like sending someone a message on a traditional pager. You know you sent it, but you do not know if they received it. The pager may not exist, may be out of the service area, may not be on, or may not be functioning. In addition, the pager network may lose the page. Unless the person pages you back, you do not know if your message was delivered. In addition, if you send multiple pages, it is possible for them to arrive out of order.

 

Another real world example that is similar to UDP, is the postal service. You can send it, but you cannot guarantee it will be delivered to the destination. It may be lost anywhere along the way, or delivered, but mutilated before delivery.

 

 

Broadcasts

 

UDP has a unique ability that is often the feature that makes it desirable. This ability is the ability to be broadcasted. Broadcasting means that a single message can be sent, but can be received by many recipients. This is not the same as multicasting. Multicasting is a subscription model where recipients subscribe and are added to a distribution list. With broadcasting, a message is sent across the network and anyone listening can receive without the need to subscribe.

 

Multicasting is similar to a newspaper delivery. Only people who subscribe to the newspaper receive it. Broadcasting is similar to a radio signal. Anyone with a receiver can tune to a specific radio station and receive it. The user does not need to notify the radio station that they wish to listen.

 

A specific broadcast IP can be calculated based on the IP of the sender and a subnet mask. However in most cases it is easiest to use 255.255.255.255 which will broadcast as far as possible.

 

Nearly all routers however are programmed to filter out broadcast messages by default. This means that messages will not pass across bridges or external routes, and the broadcast will be limited to the local LAN network.

 

Packet Sizes

 

Most operating systems allow UDP packet sizes of 32K or even 64K. However typically routers will have smaller limits. UDP packets can only be as big as the maximum allowable size that is permitted by any router or network device along the route that the UDP packet must travel. There is no way to know this value or predict it.

 

Because of this, it is recommended that UDP packets be kept at 8192 bytes or less if you are transmitting beyond the local LAN. In many cases even this may be too large of a value. To be absolutely sure, keep all UDP packets 1024 bytes or less.

 

 

TIdUDPClient

 

TIdUDPClient is the base UDP client for sending UDP packets to other destinations. The most commonly used method is Send, which uses the Host and Port properties to send a UDP packet. It accepts a string as an argument.

 

There is also a SendBuffer method which performs the same task as Send, except that it accepts a Buffer and Size as arguments.

 

TIdUDPClient can also be used as a server of sorts to wait and receive incoming UDP packets on an individual basis.

 

TIdUDPServer

 

Since UDP is connectionless, TIdUDPServer operates differently than TIdTCPServer. TIdUDPServer does not have any modes similar to TIdSimpleServer, but since UDP is connectionless, TIdUDPClient does have single use listening methods.

 

TIdUDPServer when active creates a listening thread to listen for inbound UDP packets. For each UDP packet received, TIdUDPServer will fire the OnUDPRead event in the main thread, or in the context of the listening thread depending on the value of the ThreadedEvent property.

 

When ThreadedEvent is false, the OnUDPRead event will be fired in the context of the main program thread. When ThreadedEvent is true, the OnUDPRead event is fired in the context of the listener thread.

 

Whether ThreadedEvent is true or false, its execution will block the receiving of more messages. Because of this the processing of the OnUDPRead event must be processed quickly.

 

 

UDP Example - RBSOD

 

Overview

 

This example will provide an example of a UDP client and a UDP server. The example is a useful application as well which can be used to spread fun around many corporate environments. However be sure to use it with care.

 

The example is named Remote BSOD invocator - or RBSOD for short. RBSOD can be used to trigger fake BSODs on colleagues (or enemies) machines. The BSOD's that are triggered are not real, and will not cause the person to lose any data. However they should give them a startle. The BSOD's can also be cleared remotely.

 

RBSOD consists of two programs. The server which is to be run on the remote machine, and the client which is used to control and trigger the BSOD's.

 

Many options are provided. Depending on the options that are selected, the BSOD can be made to appear quite authentic, or it can be made to appear as a joke. However when they are made to appear as a joke it will take the user a few seconds to understand this and provide them with an initial startle.

 

The client (RBSOD) looks like this:

 

 

 

 

Usage

 

The following options are provided.

 

IP Address

Specifies the IP address or host name of the computer you that wish to trigger the BSOD. The server must be installed and running on the remote computer.

 

If this field is left empty, the message will be broadcast across the local subnet (in most cases your local LAN) and all computers which have the server installed and running will display the BSOD.

 

Message

Message is the error message that will be displayed to the user in the BSOD screen. The default message is:

 

A fatal exception OE has occurred at 0028:C0011E36 in VXD VMM(01)00010E36. The current application will be terminated.

 

This text was taken from a real BSOD, and will appear quite real to the user.

 

Many Japanese Haiku's (A special form of a Japanese poem) are also included for fun. There are many very funny ones to choose from. Here two of my favorites:

 

Windows crashed again. I am the Blue Screen of Death. No one hears your screams.

 

Three things are certain: Death, taxes, and lost data. Guess which has occurred.

 

Imagine the look on your colleagues faces after they realize they have been fooled. Be very careful when you perform this prank on Dilbert type bosses or Visual Basic programmers however. They may not realize that it is a prank.

 

These messages are contained in messages.dat, and you can add your own as well.

 

Use a Custom Message

If this option is checked, another text box will appear and you can enter a custom message. This option is useful for providing interactive or relevant BSOD messages. For example if your boss is wearing an unusually tacky suit one day you might trigger this message:

 

Ugly brown suit error. I cannot continue in such company.

 

Show Any Key

This option can be used to additional humor to the prank. The BSOD will initially prompt the user with "Press any key to continue _". This is the same as a normal BSOD. However if this option is checked, after they press a key it will then further prompt them with flashing text "Not that key, press the ANY key!".

 

This option is sure to keep Visual Basic programmers and Dilbert bosses busy for hours hunting for the Any key. This option is best used just prior to leaving for the airport on a long business trip, or when you need to keep them busy for a while.

 

Show Trademark

If this option is used, in the bottom corner of the screen the following the following text will be displayed in small but readable text:

 

* The BSOD is a trademark of the Microsoft Corporation.

 

Show

The show button will trigger the BSOD on the remote computers.

 

Clear

Clear can be used to remotely clear the triggered BSODs. Normally you will let the end user clear their own BSOD, but in some cases you may wish to clear it remotely.

 

 

 

Server

 

Let's take a look at the server first.

 

Installation

The server is named svchost instead of RBSODServer or some other sensible name. This is so that you can install it easily on other computers while hiding it. svchost is a normal windows executable of which normally multiple copies are executing. If you look in task manager now, you are likely to see four entries for this executable. Since you will put this special version in its own directory, it will not interfere with the windows one, but will appear the same as the normal when viewed in task manager.

 

The server does not have any windows and will not appear in the system tray or task tray. If you want to stop it, use the task manager and select the svchost that is running as the logged on user. The system svchosts will be running as SYSTEM. When you have selected your version, you can use END TASK.

 

For most installations simply copy the compiled version of the server to the target computer (compile without packages for easiest deployment) and run it. It will stay in memory until the user restarts their computer. After they restart, the program will not reload. If you want the program to automatically reload you can add it to the users start up group or use registry entries for a stealthier start.

 

Source Code

The server consists of two units, Server.pas and BSOD.pas. BSOD.pas contains a form that is used to display the BSOD screen. BSOD.pas does not contain any Indy code and thus will not be covered here.

 

Server.pas is the main application form and contains a single UDP server. The port property has been set to 6001 and the active property has been set to True. When the application runs, it will immediately begin listening on port 6001 for incoming UDP packets.

 

As discussed previously UDP is similar to a pager. Because of this there are no connections required to receive data. Data packets simply arrive as one piece. For each UDP packet that is received, the UDP server will fire the OnUDPRead event. No other events are needed to implement a UDP server. When the OnUDPRead event is fired, the complete packet will have been received and is now available for use.

 

The OnUDPRead event passes in three arguments:

1. ASender: TObject - This is the component which fired this event. This is only useful multiple UDPServers have been created and are sharing a single method as an event. This is rarely used or needed.

2. AData: TStream - This is the main argument and it contains the packet. UDP packets can contain text and/or binary data. Because of this Indy represents them as a stream. To access the data, simply use the read methods of the TStream class.

3. ABinding: TIdSocketHandle - This is useful for advanced options to retrieve information about the binding that was used to receive the packet. This is only useful if you have created bindings.

 

Here is what the OnUDPRead event looks like for the RBSOD server:

 

procedure TformMain.IdUDPServer1UDPRead(Sender: TObject; AData: TStream;

ABinding: TIdSocketHandle);

var

LMsg: string;

begin

if AData.Size = 0 then begin

formBSOD.Hide;

end else begin

// Move from stream into a string

SetLength(LMsg, AData.Size);

AData.ReadBuffer(LMsg[1], Length(LMsg));

//

formBSOD.ShowBSOD(Copy(LMsg, 3, MaxInt)

, Copy(LMsg, 1, 1) = 'T'

, Copy(LMsg, 2, 1) = 'T');

end;

end;

 

Notice that there is an if statement that checks to see if the size is 0. It is legal to send and receive empty UDP packets. In this case it is used to signal the server to clear the BSOD.

 

If the size is not 0, the data is read into a local string using TStream.ReadBuffer.

 

The UDP server does not use an individual thread for each packet, so OnUDPRead events occur sequentially. By default OnUDPRead is fired in the main thread so forms and other GUI controls can be accessed safely.

 

 

Client

 

The RBSOD client is even simpler than the server. The RBSOD client consists of one form: Main.pas. Main.pas contains several events but most of them are related to the user interface and should be self explanatory.

 

The relevant Indy code in the RBSOD client is this excerpt which is taken from the OnClick of the Show button.

 

IdUDPClient1.Host := editHost.Text;

IdUDPClient1.Send(

iif(chckShowAnyKey.Checked, 'T', 'F')

+ iif(chckTrademark.Checked, 'T', 'F')

+ s);

 

The first line sets the host that the UDP packet will be sent to. The port has already been set at design time using the property inspector and matches that of the server with the value of 6001.

 

The next line uses the Send method to send the UDP packet. Since UDP is connectionless, any data must either be sent as multiple packets, or packaged into a single packet. If multiple packets are sent, it is the developers responsibility to coordinate and reassemble them. This is not as trivial a task as it appears, so unless you are sending large amounts of data it is much easier to assemble the data into a single packet.

 

The argument passed to send will be immediately sent as a UDP packet and thus when it is called, all data that you want to be sent must be passed in a single call.

 

Indy also contains an overloaded SendBuffer method to send data using buffers.

 

In the case of RBSOD the protocol simply consists of two characters that specify options to show the trademark, and show any key, followed by the actual message to display.

 

The only other Indy code in the RBSOD client is in the OnClick for the Clear button and it is nearly identical to the previous excerpt.

 

 

 

Protocols

 

SMTP Protocol

 

This chapter will provide a basic introduction to the SMTP protocol.

 

What is SMTP?

 

SMTP is the Simple Mail Transport Protocol. SMTP used only for sending mail, not for receiving. To receive mail, another protocol such as POP3, or IMAP4 must be used.

 

 

 

How does SMTP work?

 

An SMTP client accepts messages that it transmits to a known SMTP server. This server then determines where the messages actually should be delivered to and then relays each individual messages to local mailboxes, or other SMTP servers. SMTP clients typically deliver all mail to a single SMTP server and let the SMTP server decide the final destination.

 

The SMTP server is like your local post office. You can use other post offices, but if you have mail for many different destinations it is much easier to give it to the local post office to let them sort it and deliver it to the destination post office rather than going to each post office yourself.

 

 

Indy SMTP Support

 

Indy has robust SMTP support, however in this section only the basics of sending a plain text mail message will be demonstrated. To send a mail message with Indy two components are needed: TIdSMTP and TIdMessage.

 

 

TIdSMTP

 

TIdSMTP is the Indy component that connects and communicates with a SMTP server.

 

TIdMessage

 

TIdSMTP only handles the communication and the SMTP protocol. TIdMessage handles all the semantics of storing, encoding, and decoding of messages and message parts. Message parts consist of headers, HTML, file attachments, and more.

 

TIdMessage is separate from TIdSMTP not only because of encapsulation, but because several other protocols such as POP3 and NNTP use the same message formats and TIdMessage thus is a common component.

 

TIdMessage also supports methods for storing and loading messages from streams and files.

 

 

Constructing the Message

 

The following code is the basic structure for constructing a message:

 

Delphi

with mesgMessage do begin

Clear;

From.Text := AFrom;

Recipients.Add.Text := ATo;

Subject := ASubject;

Body.Text := AMsg;

end;

 

C#

Indy.Sockets.IndyMessage.Message LMsg = new Indy.Sockets.IndyMessage.Message();

LMsg.From.Text = AFrom;

LMsg.Recipients.Add().Text = ATo;

LMsg.Subject = ASubject;

LMsg.Body.Text = AMsg;

 

A call to the Clear method is necessary only if a single TIdMessage is being reused. Clear resets the message content and other fields to their default or empty states.

 

It is actually legal to send messages without From, Subject, and Body. However such a message is not very useful and many servers will reject it either as either faulty, or probable spam. Thus these properties should be considered the minimum requirements for sending a message.

 

For more advanced messages TIdMessage has properties for CC, BCC, Attachments, HTML and more.

 

 

Sending the Message

 

Once a message has been constructed it must be delivered to an SMTP server. This is done using TIdSMTP. The following code is the basic form for sending a message:

 

Delphi

with smtpSendMail do begin

Host := 'mail.mycompany.com';

Connect; try

Send(mesgMessage);

finally Disconnect; end;

end;

 

C#

SMTP LSMTP = new SMTP();

LSMTP.Host = "mail.mycompany.com";

LSMTP.Connect();

try {

LSMTP.Send(LMsg);

}

finally {

LSMTP.Disconnect();

}

 

The host must be set so that the TIdSMTP knows where to send the messages to. In the case of SMTP, the host can be thought of as the address of the local post office.

 

The Send method accepts one argument which specifies the TIdMessage to send.

 

In this example only one message is sent, however the SMTP protocol allows for multiple messages so it is not necessary to connect and disconnect if multiple messages are to be sent. To send multiple messages simply make additional calls to the Send method while connected. Multiple TIdMessage components can be used, or an existing one can be modified between calls to Send.

 

 

 

QuickSend

 

When only one message needs to be sent, and the message is a basic text message a simpler but more limited form is available. QuickSend is a class method of TIdSMTP. No instances of TIdMessage, or even a TIdSMTP needs to be created.

 

QuickSend constructs a TIdMessage, TIdSMTP and performs all necessary communication with an SMTP server including connection and disconnection.

 

The declaration of QuickSend is as follows:

 

class procedure QuickSend(const AHost, ASubject, ATo, AFrom, AText: string);

 

Example usage:

 

Delphi

TIdSMTP.QuickSend('mail.mycompany.com', 'Message from me', 'you@yourcompany.com'

, 'me@mycompany.com', 'Hello! I am sending you mail.');

 

C#

SMTP.QuickSend(null, "mail.mycompany.com", "Message from me", 'you@yourcompany.com'

, "me@mycompany.com", "Hello! I am sending you mail.");

 

Be sure to note that this is a class method. You do not need to create an instance of TIdSMTP. Instead the method is called as part of the class itself.

 

C# does not support class methods in the same manner as Delphi, so simplly pass null as the first argument. This argument is not necessary in Delphi as it is implicitly passed by the compiler.

 

 

 

Send Mail Demo

 

A very basic demo of sending a simple mail message is available as SendMail.

 

 

In addition to the basics covered in this chapter, the demo also makes use of the OnStatus event. The OnStatus event is a core event of Indy but implemented differently by each protocol. OnStatus is fired at various times during calls to Connect, Send, and Disconnect. Each time a text messages is passed that explains what is occurring. OnStatus is designed to provide user interface updates, or logging. It its not designed for progmatic interpretation of state. In the SendMail demo, the messages from OnStatus are displayed into a listbox.

 

In this demo the OnStatus even has been defined at design time and is as follows:

 

Delphi

procedure TformMain.smtpSendMailStatus(ASender: TObject;

const AStatus: TIdStatus; const AStatusText: String);

begin

Status(AStatusText);

end;

 

procedure TformMain.Status(AMsg: string);

begin

lboxStatus.ItemIndex := lboxStatus.Items.Add(AMsg);

// Allow the listbox to repaint

Application.ProcessMessages;

Application.ProcessMessages;

Application.ProcessMessages;

end;

 

C#

private void Status(string AMessage) {

lboxStatus.Items.Add(AMessage);

// Allow the listbox to repaint

Application.DoEvents();

Application.DoEvents();

Application.DoEvents();

}

 

private void SMTPStatus(object ASender, Indy.Sockets.IndyComponent.Status AStatus, string AText) {

Status(AText);

}

 

When the Send Mail button is pressed the following code is executed to send the message:

 

Delphi

procedure TformMain.butnSendMailClick(Sender: TObject);

begin

butnSendMail.Enabled := False; try

with mesgMessage do begin

Clear;

From.Text := Trim(editFrom.Text);

Recipients.Add.Text := Trim(editTo.Text);

Subject := Trim(editSubject.Text);

Body.Assign(memoMsg.Lines);

end;

with smtpSendMail do begin

Host := Trim(editSMTPServer.Text);

Connect; try

Send(mesgMessage);

finally Disconnect; end;

end;

Status('Completed');

finally butnSendMail.Enabled := True; end;

end;

 

C#

private void butnSendMail_Click(object sender, System.EventArgs e) {

butnSendMail.Enabled = false;

try {

Indy.Sockets.IndyMessage.Message LMsg = new Indy.Sockets.IndyMessage.Message();

LMsg.From.Text = textFrom.Text.Trim();

LMsg.Recipients.Add().Text = textTo.Text.Trim();

LMsg.Subject = textSubject.Text.Trim();

LMsg.Body.Text = textMsg.Text;

 

SMTP LSMTP = new SMTP();

LSMTP.OnStatus += new Indy.Sockets.IndyComponent.TIdStatusEvent(SMTPStatus);

LSMTP.Host = textSMTPServer.Text.Trim();

LSMTP.Connect();

try {

LSMTP.Send(LMsg);

Status("Completed");

}

finally {

LSMTP.Disconnect();

}

}

finally {

butnSendMail.Enabled = true;

}

}

 

 

 

More Information

 

7    RFC 2821 - Simple Mail Transfer Protocol - ftp://ftp.rfc-editor.org/in-notes/rfc2821.txt

 

HTTP Protocol

 

HTTP is the HyperText Transport Protocol and is responsible for serving web pages and other content to web browsers. Since its inception it has been also been used for SOAP, XML transport, audio, video, and much more.

 

HTTP does not maintain persistent connections as other protocols do. In HTTP 1.0 only one request can be served per connection, and each request must use a new connection. HTTP 1.1 allows for batch requests, but still closes the connection after each set of requests has been fullfilled.

 

Because of the "hit and run" nature of HTTP, HTTP connections must be treated as stateless. While the stateless nature can make developing session tracking more difficult, it makes HTTP ideally suited for high capacity file serving which was its original purpose, and still its primary use.

 

 

The Firewall Friendly Protocol

 

HTTP is usually associated with web pages but can be used for many other purposes. Many corporations have set up strict firewalls that by default only allow mail and web traffic to traverse the firewall. If you have worked behind one of these firewalls you know that asking the firewall administrator to open up new protocols is usually an exercise in futility. Because of this HTTP has been adapted as the firewall friendly protocol and many higher level protocols now ride on top of HTTP allowing them to be accessed across firewalls. One example of this is SOAP. SOAP is not restricted to HTTP but it is certainly the most common transport for it.

 

HTTP pages can be static, or dynamic. This could certainly be done using an ISAPI plug-in for Microsoft IIS, or a DSO for Apache. However with Indy you have more flexibility, better debugging options, and a completely functional dedicated HTTP server that you can distribute independently.

 

Many commercial vendors use this feature to ship commercial products that do not require IIS or Apache. IIS has taken a real beating recently with security flaws and as a result many corporations are now shying away from it. And for smaller shops it can be a maintenance and security issue to install it. Even if the customer has an existing web server installed the Indy based web server can be configured to run on an alternate port in parallel to the existing web server. Three such commercial products are IntraWeb from Atozed Software, Apollo Server from Vista Software, and Rave Server from Nevrona Designs. All of these stand alone HTTP based servers are built using Indy. Borland also uses Indy for the SOAP server in Kylix, and for the SOAP services and WebSnap debugger in Delphi 7.

 

 

Demo - Minimal Web Server

 

Building a minimal web server in Indy is really quite easy. Start with a new application and follow these steps:

 

1. Add a TIdHTTPServer component to the form.

2. Set its Active property to True.

3. Create a OnCommandGet event and enter the following code:

 

C#

AResponseInfo.ContentText = "Hello World. It is " + DateTime.Now.ToShortTimeString();

 

Delphi

AResponseInfo.ContentText := 'Hello World. It is ' + TimeToStr(Time);

 

For C#, there are a few more lines of code to be written since the class must be created at run time. Full source is available as a download.

 

Run the application. All you should see is an empty form, however you now have a functional web server. To see this, launch your web browser and enter this URL:

http://127.0.0.1/

 

If you have a web server running on your machine this web server will conflict with it and you will receive an error when you run the application. To avoid this, temporarily shut down your web server. You can also change the port that this custom web server listens on. To do this, reset the application and change the HTTPServer's DefaultPort property. Try 8989. Now run the application again. The consequence is that you will now need to change all URLs to include the port, such as is shown here:

 

http://127.0.0.1:8989/

 

As you should remember from earlier sections, 127.0.0.1 is a special network address and equivalent to "self". This will cause the request to be served by your machine no matter what your machine's real network address is. You should see a web page returned similar to this:

 

Hello World. It is 9:19:34 PM

 

Force the web page to refresh and you will see that the time changes accordingly and is a truly dynamic web page.

Congratulations! You just built a dynamic web server.

 

This demo is available a the Indy Demo Playground as Minimal Web Server.

 

 

Demo - Eliza Web

 

This demo presents a simple web (HTTP) server built with Indy and demonstrates the TIdHTTPServer component, and its built in session management feature.

 

This demo uses a HTTP server, but a specialized one. Indy's HTTP server can certainly be used to build a web server which serves up static web pages, but why reinvent the wheel? Indy's HTTP server is most useful for developing custom HTTP servers, or custom protocols built around HTTP.

 

Back in the 1970's in the early days of personal computing a program called Eliza was published in Creative Computing. Creative Computing was the computing magazine of the day and contained a lot of interesting content. The computing power of a personal computer in these days was very limited so all source code submissions were printed and users merely typed them in. Many submissions were games and the like but many were not. One such submissions was Eliza. Eliza was a computer psychiatrist that used a very simple form of language processing to take human input and provide a two way conversation. Over the years Eliza has been ported to many languages. I found some old basic code and in some language called Inform. From these two sources I have ported it to Delphi. I plan to make further improvements later.

This article will not focus on Eliza other than to use it as the dynamic part of this demonstration. In this article we will create Eliza Web, the HTTP shrink. Eliza is only a few dozen lines of code plus some data but can produce some amazing dialog. This is why it started such a firestorm of interest in the 1970's and has persisted in popularity over the years. Here is an example Eliza conversation:

 

User: What makes you think I have a problem?

Eliza: We were discussing you--not me.

User: I want to talk about women.

Eliza: Why do you want to talk about women?

User: Because I want to talk about women.

Eliza: Suppose you soon got to talk about women?

 

And so on... Eliza can carry on some pretty interesting conversations. Its responses are pretty amazing for a few dozen lines of code.

 

 

TEliza

 

Do not let your significant other walk in during reading this article she might worry that you are trying to make a virtual significant other. Anyways, TEliza has a simple interface. For the sake of this demo, just treat it like a black box. I will not cover the implementation, but only the interface.

 

The interface is quite simple. Construct a TEliza and keep it in memory as it keeps internal state. Then simply call the TalkTo(AMsg: string) function for each question or statement you want Eliza to respond to. The function returns a string which contains Eliza's response. TEliza is self contained in Eliza.pas, with plugin personalities in other units.

 

 

Building Eliza Web

 

I built the initial Eliza server just as I did the minimal server before and then expanded on it. Eliza server has three events implemented and one helper method. I will cover them one by one.

 

The first event is the form OnCreate. It merely initializes some variables that are used later. It initializes FHTMLDir which points to a HTML subdirectory where HTML and graphics files reside. It then also initializes FTemplate by loading the contents of eliza.html. This is a form of caching. Without FTemplate Eliza server would need to read eliza.html many times needlessly.

 

The next event is the OnCommandGet which handles requests for HTTP request, in our case HTML documents and images. In the minimal server we always served the same document and ignored what specific document the web browser was requesting. In Eliza server we look at the URL being requested and act appropriately.

 

procedure TForm1.IdHTTPServer1CommandGet(

AThread: TIdPeerThread;

ARequestInfo: TIdHTTPRequestInfo;

AResponseInfo: TIdHTTPResponseInfo);

var

LFilename: string;

LPathname: string;

begin

LFilename := ARequestInfo.Document;

if AnsiSameText(LFilename, '/eliza.html') then begin

Ask(ARequestInfo, AResponseInfo);

end else begin

if LFilename = '/' then begin

LFilename := '/index.html';

end;

LPathname := FHTMLDir + LFilename;

if FileExists(LPathname) then begin

AResponseInfo.ContentStream := TFileStream.Create(LPathname, fmOpenRead + fmShareDenyWrite);

end else begin

AResponseInfo.ResponseNo := 404;

AResponseInfo.ContentText := 'The requested URL ' + ARequestInfo.Document

+ ' was not found on this server.';

end;

end;

end;

 

OnCommandGet passes three arguments: AThread, ARequestInfo, and AResponseInfo. AThread is the thread that the request is executing under and is a part of all Indy servers. It is rarely used in HTTP server events. ARequestInfo is an object that contains the request information such as the document portion of the URL, cookies, HTTP parameters, request parameters and more. AResponseInfo is an object for sending a response back to the web browser. It also allows you to set cookies, return error codes, perform redirects, and more.

 

Eliza's OnCommandGet has two main branches. One to handle the interaction with Eliza, and one to handle static HTML files and images.

 

For the interactive branch (document eliza.html) processing is deferred to the Ask method which is described later.

All other requests are treated as requests for static documents. Requests filled by looking for matching files in the HTML subdirectory (FHTMLDir). If a file is found the file is returned using AResponseInfo.ContentStream.

 

Remember, the OnCommandGet event is threaded and many requests may be occurring simultaneously. In addition some web browsers may have slow connections and the TFilestream will remain in existence until the complete file is transmitted. Because of this we must open the file read only and mark it with a share mode that will allow other threads to concurrently open the same file without trouble.

 

If the requested file cannot be found an HTTP 404 is returned to the web browser. HTTP error 404 means that the file cannot be found. For this error error text must also be returned.

 

 

State Management

 

HTTP is a stateless protocol. This means that from request to request you typically cannot associate requests from the same web browser with previous requests. This makes it quite difficult to keep track of information on the server about the user. The two most common ways are to embed the information in each HTML page returned to the user, or to use cookies. Both have advantages and disadvantages but both are typically tedious for the user.

 

Indy has an automatic state management feature that can be used to alleviate the work on the programmer. It performs its work by issuing a cookie to the browser and using that cookie to identify the specific web browser. But do not worry, you do not need to know anything about cookies to take advantage of it.

 

With Indy HTTP state management a session object is created. You can then use that object to store information in a TStrings property named Content. Eliza server will use this method. TEliza requires state to be kept about the user to properly analyze past questions along with the current. Because of this we cannot create a new TEliza object for each request and must use the same one each time a specific user returns with another request.

 

The basics of using Indy HTTP state management are as follows:

1. Set AutoStartSession to True. This causes sessions to be started automatically without instructions at specific points from the developer.

2. Set SessionState to True. This enabled Indy's HTTP state management.

3. Set a value for SessionTimeout. This is the period of inactivity after which a session will be destroyed, specified in milliseconds.

4. Define an OnSessionStart event.

 

In Eliza's OnSessionStart a TEliza is created and stored as an item in the session object's Content property. It can then be accessed in the OnCommandGet later and on successive requests.

 

 

Running Eliza Web

 

Eliza is used in a similar fashion to our minimal server. Simply run Eliza and then enter as a URL into your browser:

 

http://127.0.0.1/

 

Since no document is specified, Eliza server assumes index.html and serves it from the HTML subdirectory. Index.html contains graphics which the web browser will request as separate requests. Eliza server will also serve those requests from the HTML subdirectory.

 

Index.html contains a link for the user to start which links to eliza.html. Eliza.html however is not served as a static file like the others. Requests for eliza.html are intercepted and served dynamically. Eliza.html is an HTML form which submits requests back to itself. It contains a special string {%RESPONSE%} which Eliza server replaces on each request with Eliza's response. This is all performed in the Ask method.

 

procedure TForm1.Ask(

ARequestInfo: TIdHTTPRequestInfo;

AResponseInfo: TIdHTTPResponseInfo);

var

LResponse: string;

LQuestion: string;

begin

LResponse := '';

LQuestion := Trim(ARequestInfo.Params.Values['Thought']);

if LQuestion <> '' then begin

LResponse := TEliza(ARequestInfo.Session.Content.Objects[0]).TalkTo(LQuestion);

end;

AResponseInfo.ContentText := StringReplace(FTemplate, '{%RESPONSE%}', LResponse, []);

end;

 

The ask method checks for the submitted form parameter named 'Thought' and passes that to the TEliza object which is stored in the session. The response is then embedded into the HTML and returned as a dynamic web page as shown below.

 

 

 

More Information

 

7    RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 - ftp://ftp.rfc-editor.org/in-notes/rfc2616.pdf

 

About the Author

 

Chad Z. Hower, a.k.a. Kudzu

"Programming is an art form that fights back"

 

Chad works for Atozed Software, and is the original author of both Internet Direct (Indy) and IntraWeb. Both Indy and IntraWeb have been licensed by Borland for inclusion in Delphi, Kylix and C++ Builder. Chad speaks at 6- 8 conferences each year in Europe and North America, writes frequently for developer magazines, and also posts free articles, programs, utilities and other oddities at Kudzu World.

 

Chad's background includes work in the employment, security, chemical, energy, trading, telecommunications, wireless, and insurance industries. Chad's area of specialty is TCP/IP networking and programming, inter-process communication, distributed computing, Internet protocols, and object-oriented programming. When not programming, he likes to bike, kayak, ski, drive, and do just about anything outdoors.

 

Chad is an ex-patriate who spends his summers in St. Petersburg, Russia, winters in Limassol, Cyprus, and travels extensively year round.



Firefox 3: Lebih Cepat, Lebih Aman, Dapat Disesuaikan dan Gratis.


Dapatkan nama yang Anda sukai!
Sekarang Anda dapat memiliki email di @ymail.com dan @rocketmail.com.

0 Comments: