Shiv Kumar
 Hobbyist Filmmaker / Editor

PART 2 - Building the Telnet Client for the Server

Not Rated YetNot Rated YetNot Rated YetNot Rated YetNot Rated Yet0votes
January 03, 2008 12:14 PM  Views: 2288   Favorited: 0 Favorite It Comments: 0
Filed Under:  Programming
Tags:  Delphi
 
In this article we will build a TCP/IP client application that can "talk" to the server application mentioned in the previous article. In other words, we're attempting to build a telnet like application of our own.

Before we begin, we need to have the right components for the job. Delphi comes with TClientSocket and TServerSocket components (Internet Palette) but we won't be using these components. There is nothing wrong with these components however. It's just a matter of choice.

The component suite we'll use in this series of articles is a suite of TCP/IP components called Indy. Indy will be a part of Delphi 6 and Kylix. Indy is an open source library of components and available for download free from the Indy web site. I suggest you download and install these components now, so you can work along with me on the remainder of this article.

Getting started

First let me explain what we are really attempting to do here. We need an application that will:
  1. Allow us to specify a host name or IP Address.
  2. Specify a port.
  3. Allow us to connect to the server.
  4. Issue commands to the server.
  5. Send "commands" or text data to the server.
  6. Receive data from the server and display it.
The application will look something like that shown in Figure 1

Figure 1: Showing a simple TCP/IP client application's User Interface.

The Project

  1. Start a new project and build a user interface that looks similar to that shown in Figure 1.
  2. From the Indy Clients Palette, drop down a TIdTCPClient component (you'll find this components to the extreme left of this palette).
  3. Change the Name of the TEdit control that will receive the Host Name to - edtHost.
  4. Change the Name of the TEdit control that will receive the Port No. to - edtPort.
  5. Change the Name of the TEdit control in which we will type in our commands/messages to - edtMessage.
  6. Change the Name of the TMemo control in which we will receive data from the server to - memRcvMessage.
The source code listing of the whole unit is shown below.
unit uMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Grids, ComCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
  IdTCPClient;

type
  TMainForm = class(TForm)
    Label1: TLabel;
    edtHost: TEdit;
    Label2: TLabel;
    edtPort: TEdit;
    Button1: TButton;
    edtMessage: TEdit;
    Label3: TLabel;
    Button2: TButton;
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    memRcvMessage: TMemo;
    StringGrid1: TStringGrid;
    IdTCPClient1: TIdTCPClient;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure edtMessageKeyPress(Sender: TObject; var Key: Char);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TRecvThread = class(TThread)
  private
    Msg : string;
    procedure ReceivedLine;
  protected
    procedure Execute; override;
  end;

var
  MainForm: TMainForm;
  RecvThread : TRecvThread;
implementation

{$R *.DFM}

{TRecvThread }
procedure TRecvThread.Execute;
begin
  while not Terminated do begin
    try
      Msg := MainForm.IdTCPClient1.ReadLn;
      Synchronize(ReceivedLine);
    except
      Terminate;
    end;
  end;
end;

procedure TRecvThread.ReceivedLine;
begin
  if Copy(Msg,1,4) = '-Err' then
    MessageDlg(Copy(Msg,5,Length(Msg)), mtError, [mbOk],0)
  else
    MainForm.memRcvMessage.Lines.Add(Msg);
end;

{TForm1}
procedure TMainForm.Button1Click(Sender: TObject);
begin
  { Assign values to the Host and Port Properties }
  with IdTCPClient1 do
  begin
    Host := edtHost.Text;
    Port := StrToInt(edtPort.Text);
    Connect;
  end;
  { Create the Thread }
  RecvThread := TRecvThread.Create(False);
  Button1.Enabled := False;
  Button2.Enabled := True;
end;

procedure TMainForm.Button2Click(Sender: TObject);
begin
  { Get the Thread to terminate and wait till it is terminated }
  RecvThread.Terminate;
  repeat
    Application.ProcessMessages;
  until RecvThread.Terminated;

  IdTCPClient1.Disconnect;
  Button1.Enabled := True;
  Button2.Enabled := False;
end;

procedure TMainForm.edtMessageKeyPress(Sender: TObject; var Key: Char);
begin
  { If the ENTER KEY is pressed, send the Text in edtMessage to the Server }
  if Key = #13 then
  begin
    IdTCPClient1.WriteLn(edtMessage.Text);
    edtMessage.Text := '';
  end;
end;

end.
The first thing you're going to notice is that we have defined a thread class of our own. This may seem complicated at first for those of you who have not worked with threads. But it's really simple in this case. The reason we have a thread class is due to the fact that sockets by nature are blocking sockets. That is when you call a method of a socket, it waits around till its done before control returns to you.

Blocking versus Non-Blocking

There are some advantages of each paradigm and programmers tend to be comfortable with one over the other. Programming with blocking sockets will tend to be more sequential in nature, while non-blocking sockets tend to make your programming more event-driven. In some cases, keeping track of events while you're in a certain other procedure in your code can tend to be cumbersome.

Winshoes

The Indy suite of socket components is of the blocking kind. Which means, for example, when we call the Connect method of the TIdClient socket, the code after this method call will not be executed until the method returns. If the socket finds the server and a connection is established, the next line of code will execute. If there was a problem, an exception will be raised. It is much simpler to work with sockets in a sequential or blocking manner since you're dependent on a server or client, as the case may be.

Similarly, when we call the ReadLn method of the TIdClient socket, we won't get control till it receives a line of data from the server. This will make our GUI unresponsive for that duration. Since that's not what we want, we create a thread and call the ReadLn method of the socket in the thread. This makes our GUI responsive, while the socket waits around for lines of data to be received from the server in the background. The Winshoes suite of components does have a component called IdAntiFreeze (an apt name!) that when dropped on a form will automatically take care of making the GUI responsive without us having to use threads. But in this series of articles, we'll do it the hard way !

The program

The Connect button's (TButton1) OnClick event:
procedure TMainForm.Button1Click(Sender: TObject);
begin
  { Assign values to the Host and Port Properties }
  with IdTCPClient1 do
  begin
    Host := edtHost.Text;
    Port := StrToInt(edtPort.Text);
    Connect;
  end;
  { Create the Thread }
  RecvThread := TRecvThread.Create(False);
  Button1.Enabled := False;
  Button2.Enabled := True;
end;
  1. First we assign the Host or Port properties their respective values.
  2. Then we call the connect method.
  3. If the connection is established with the specified host on the specified port (in other words a socket) we create an instance of our TRecvThread object.
  4. Disable the Connect button so as to prevent the user from trying to establish a connection after a connection is already established.
  5. Enable the Disconnect button (TButton2) so as to allow the user to disconnect from the server.
That was simple!

The Disconnect button's (TButton2) onClick event:

procedure TMainForm.Button2Click(Sender: TObject);
begin
  { Get the Thread to terminate and wait till it is terminated }
  RecvThread.Terminate;
  repeat
    Application.ProcessMessages;
  until RecvThread.Terminated;

  IdTCPClient1.Disconnect;
  Button1.Enabled := True;
  Button2.Enabled := False;
end;
  1. Here we call the thread's Terminate method in an attempt to get the thread to terminate.
  2. Wait till the thread is terminated.
  3. Call the Disconnect method of the Client Socket.
  4. Enable the Connect button.
  5. Disable the Disconnect button.

Sending Data to the Server

To send data (text) to the server, we need to call the WriteLn method of the TIdClient socket. The Data we want to send in this case is the text of the edtMessage (TEdit) control. We could have provided another TButton for this purpose and had the following code in its onClick event:
IdClient1.WriteLn(edtMessage.Text);
But instead, I've opted to be able to send the data to the server when one presses the ENTER key. It's just a matter of convenience for the user. For this purpose we use the OnKeyPress event of the edtMessage control:
procedure TMainForm.edtMessageKeyPress(Sender: TObject; var Key: Char);
begin
  { If the ENTER KEY is pressed, send the Text in edtMessage to the Server }
  if Key = #13 then
  begin
    IdTCPClient1.WriteLn(edtMessage.Text);
    edtMessage.Text := '';
  end;
end;
After the data is sent to the server, we clear out the text in the edtMessage control. Again, just a convenience for the user.

Once you've done all the coding, you can run the application. Type in - www.matlus.com for the Host Name. Type in - 3232 for the Port Click the Connect button. If all goes well, you should see this data in the Memo.

 OK Welcome to the TCP/IP Tutorial. This application is now an NT Service.- Shiv
Commands Understood -
SQL: <standard SQL for the Customer Table from DBDEMOS>
ABOUT <responds with an About Message>
BYE 
QUIT 
Any other strings will be returned "as is" with the DataTime Stamp

Why www.matlus.com and 3232 ?

Remember, that TCP/IP is a connection oriented protocol. For a connection to be established, we need a TCP/IP Client (application), and a TCP/IP Server (application). The Server should be "listening" for a connection request on a certain port. The Client should know the IP address of the server and Port (the combination of an IP Address and Port is also known as a socket) on which the server is listening before it can attempt to make a connection.

For this purpose, I have a server (application) running on my server (machine) at www.matlus.com, listening on port 3232. Eagerly waiting for someone to make contact.

Comments have been Disabled for this post





Leave A Comment

Shiv Kumar
Gainesville, Virginia,
United States
Member Bio Member Skills/Specialization

Bio

close

Specializations

close
Photographer
Landscape
Nature
Portrait
Videographer/Cinematographer
Interview
Landscape
Nature
Portrait
 
Privacy Policy | Terms Of Service | Contact Us | Support | Help/FAQ | News