Thursday, September 9, 2010

Custom Client-Server application with Delphi 2010 and Indy 10


I'm pretty sure you've tried and failed at least one time to implement a custom protocol with Indy, am I right?! of course you did...
I'm glad I caught your attention, now let's start creating our client-server application using Indy.
In order to implement a protocol we need to think what we want to achieve, therefore we need to define the protocol commands so that the client can communicate with server and vice-versa.
Create a new unit and save it as "uDGProtocol.pas"
We define the commands as
type
  TCommand = (
    cmdConnect,
    cmdDisconnect,
    cmdMessageBroadcast,
    cmdMessagePrivate,
    cmdScreenShotGet,
    cmdScreenShotData);
Now we need to define the client information holder, basically a structure which holds the client user name & a ID
type
  TClient = record
    UserName: string[50];
    ID: TDateTime;
  end; // TClient = record
The protocol will be defined as a structure which contains the following members: "Command", "Sender", "Receiver" and "DataSize"
type
  TProtocol = record
    // the command
    Command: TCommand;
    // sender information
    Sender: TClient;
    // receiver
    Receiver: TClient;
    // additional data
    DataSize: Integer;
  end; // TProtocol = record
TIdTCPServer has a event called "OnExecute"(we use this event to process commands) which passes a parameter called "AContext" of type "TIdContext" we will define our custom client context which will do most of the work for us
type
  TClientContext = class(TIdServerContext)
  private
    // we use critical section to ensure a single access on the connection
    // at a time
    FCriticalSection: TCriticalSection;
    // client information
    FClient: TClient;
  public
    constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn;
      AList: TThreadList = nil); override;
    destructor Destroy; override;
  public
    // enter critical section
    procedure Lock;
    // leave critical section
    procedure Unlock;
    // broadcast a buffer to connected clients
    procedure BroadcastBuffer(const ABuffer: TBytes);
    // send a buffer to a specific client
    procedure SendBuffer(const ABuffer: TBytes; const AReceiverID: TDateTime);
    // send all clients data to this client when he connects
    procedure SendClientList;
  public
    property Client: TClient read FClient write FClient;
  end;
On the client side we need three to define three custom events and a listener thread which will read from the InputBuffer of the TIdTCPClient constantly and whenever we have some data the listener thread will process it and act accordingly
type
  // connect/disconnect event
  TClientStatus = procedure (const AClient: TClient) of Object;

  // on message event
  TClientMessage = procedure (const AClient: TClient; const AMessage: string) of Object;

  // on screen shot receive event
  TClientScreenShot = procedure (const AClient: TClient; AImage: TPngImage) of Object;

// our custom listener thread for the client
type
  TClientThread = class(TThread)
  private
    // the TCP client
    FTCPClient: TIdTCPClient;
    // our client information
    FClient: TClient;
    // temporary client data holder
    FClientSender: TClient;
    // temporary buffer for message or screen shot
    FTempBuffer: TBytes;
    // temporary message holder
    FTempMessage: string;
    // a critical section
    FCriticalSection: TCriticalSection;
    // a client is connected
    FOnClientConnect: TClientStatus;
    // a client is disconnected
    FOnClientDisconnect: TClientStatus;
    // receive a message
    FOnClientMessage: TClientMessage;
    // receive a screen shot
    FOnClientScreenShotGet: TClientScreenShot;
    // procedures that will be executed in synchronization with main thread
    procedure DoClientConnect;
    procedure DoClientDisconnect;
    procedure DoClientMessage;
    procedure DoClientScreenShotSend;
    procedure DoClientScreenShotGet;
  public
    // constructor and destructor
    constructor Create(ATCPClient: TIdTCPClient);
    destructor Destroy; override;
  protected
    procedure Execute; override;
  public
    // enter critical section
    procedure Lock;
    // leave critical section
    procedure Unlock;
    // notify clients that we're connected
    procedure SendConnected;
    // notify clients that we disconnect
    procedure SendDisconnected;
    // broadcast a message
    procedure SendMessageBroadcast(const AMessage: string);
    // send a private message
    procedure SendMessagePrivate(const AReceiver: TClient; const AMessage: string);
    // send a screen shot request to a client
    procedure SendScreenShotReq(const AReceiver: TClient);
  public
    property ClientData: TClient read FClient write FClient;
    // events
    property OnClientConnect: TClientStatus read FOnClientConnect write FOnClientConnect;
    property OnClientDisconnect: TClientStatus read FOnClientDisconnect write FOnClientDisconnect;
    property OnClientMessage: TClientMessage read FOnClientMessage write FOnClientMessage;
    property OnClientScreenShotGet: TClientScreenShot read FOnClientScreenShotGet write FOnClientScreenShotGet;
  end;
Since Indy 10 is writing array of bytes on connection we need to define some helper methods like
// converts the protocol structure to an array of bytes
function ProtocolToBytes(const AProtocol: TProtocol): TBytes;
begin
  // set the length of result to the length of the protocol
  SetLength(Result, szProtocol);
  // move a block of memory from AProtocol to Result
  Move(AProtocol, Result[0], szProtocol);
end;

// converts a array of bytes to our protocol
function BytesToProtocol(const ABytes: TBytes): TProtocol;
begin
  // move a block of memory from ABytes to Result
  Move(ABytes[0], Result, szProtocol);
end;

// fills the memory with zero
procedure InitProtocol(var AProtocol: TProtocol);
begin
  FillChar(AProtocol, szProtocol, 0);
end;

// sets the length of the array of bytes to zero
procedure ClearBuffer(var ABuffer: TBytes);
begin
  // set the length to zero
  SetLength(ABuffer, 0);
end;
In the implementation section of "uDGProtocol.pas" unit we implement TClientContext class
{ TClientContext }

procedure TClientContext.BroadcastBuffer(const ABuffer: TBytes);
var
  // loop variable
  Index: Integer;
  // client list, holds TClientContext objects
  LClients: TList;
  // temporary client context reference
  LClientContext: TClientContext;
begin
  // lock the client list
  LClients := FContextList.LockList;
  try
    // for each client
    for Index := 0 to LClients.Count -1 do begin
      // store locally the current client in the list
      LClientContext := TClientContext(LClients[Index]);
      // lock it
      LClientContext.Lock;
      try
        // write the buffer
        LClientContext.Connection.IOHandler.Write(ABuffer);
      finally
        // unlock
        LClientContext.Unlock;
      end; // tryf
    end; // for Index := 0 to LClients.Count -1 do begin
  finally
    // unlock client list
    FContextList.UnlockList;
  end; // tryf
end;

constructor TClientContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn;
  AList: TThreadList);
begin
  inherited Create(AConnection, AYarn, AList);
  // create the critical section
  FCriticalSection := TCriticalSection.Create;
end;

destructor TClientContext.Destroy;
begin
  // free and nil critical section
  FreeAndNil(FCriticalSection);
  inherited;
end;

procedure TClientContext.Lock;
begin
  FCriticalSection.Enter;
end;

procedure TClientContext.SendBuffer(const ABuffer: TBytes;
  const AReceiverID: TDateTime);
var
  // loop variable
  Index: Integer;
  // client list, holds TClientContext objects
  LClients: TList;
  // temporary client context reference
  LClientContext: TClientContext;
begin
  // lock client list
  LClients := FContextList.LockList;
  try
    // search for the target client by ID
    for Index := 0 to LClients.Count -1 do begin
      LClientContext := TClientContext(LClients[Index]);
      if LClientContext.Client.ID = AReceiverID then begin
        // we found our target client, lock it
        LClientContext.Lock;
        try
          // write the buffer
          LClientContext.Connection.IOHandler.Write(ABuffer);
        finally
          // unlock client
          LClientContext.Unlock;
        end; // tryf
        // break loop, we've found our target client
        Break;
      end; // if LClientContext.Client.ID = AReceiverID then begin
    end; // for Index := 0 to LClients.Count -1 do begin
  finally
    // unlock client list
    FContextList.UnlockList;
  end; // tryf
end;

procedure TClientContext.SendClientList;
var
  // loop variable
  Index: Integer;
  // a buffer
  LBuffer: TBytes;
  // client list
  LClients: TList;
  // protocol structure
  LProtocol: TProtocol;
  // temporary client context reference
  LClientContext: TClientContext;
begin
  // clear the protocol structure
  InitProtocol(LProtocol);
  // set command
  LProtocol.Command := cmdConnect;
  // lock client list
  LClients := FContextList.LockList;
  try
    // for each connected client
    for Index := 0 to LClients.Count -1 do begin
      // store it temporarly
      LClientContext := TClientContext(LClients[Index]);
      // if the client is not this client
      if LClientContext.Client.ID <> Self.Client.ID then begin
        // set the sender
        LProtocol.Sender := LClientContext.Client;
        // covert protocol to array of bytes
        LBuffer := ProtocolToBytes(LProtocol);
        Lock;
        try
          // write the buffer
          Self.Connection.IOHandler.Write(LBuffer);
        finally
          Unlock;
        end; // tryf
      end;
    end; // for Index := 0 to LClients.Count -1 do begin
  finally
    // unlock client list
    FContextList.UnlockList;
    ClearBuffer(LBuffer);
  end; // tryf
end;

procedure TClientContext.Unlock;
begin
  FCriticalSection.Leave;
end;
and the listener thread
{ TClientThread }

constructor TClientThread.Create(ATCPClient: TIdTCPClient);
begin
  // set reference to the TCP client
  FTCPClient := ATCPClient;
  // create a critical section instance
  FCriticalSection := TCriticalSection.Create;
  inherited Create(True);
end;

destructor TClientThread.Destroy;
begin
  // free and nil the critical section
  FreeAndNil(FCriticalSection);
  // clear the temporary message
  FTempMessage := '';
  inherited;
end;

procedure TClientThread.DoClientConnect;
begin
  // check if the event is assign
  if Assigned(FOnClientConnect) then
    // call it
    FOnClientConnect(FClientSender);
end;

procedure TClientThread.DoClientDisconnect;
begin
  // check if the event is assign
  if Assigned(FOnClientDisconnect) then
    // call it
    FOnClientDisconnect(FClientSender);
end;

procedure TClientThread.DoClientMessage;
begin
  // check if the event is assign
  if Assigned(FOnClientMessage) then
    // call it
    FOnClientMessage(FClientSender, FTempMessage);
end;

procedure TClientThread.DoClientScreenShotGet;
var
  // temporary memory stream
  LStream: TMemoryStream;
  // we send, receive PNG images
  LPngImage: TPngImage;
begin
  // create a memory strema instance
  LStream := TMemoryStream.Create;
  // create a png image instance
  LPngImage := TPngImage.Create;
  Lock;
  try
    // the screen shot is saved in FTempBuffer, write it to stream
    LStream.Write(FTempBuffer[0], Length(FTempBuffer));
    // reset the position of the stream to the begining
    LStream.Position := 0;
    // load the png image from the stream
    LPngImage.LoadFromStream(LStream);
    // if the event is assigned
    if Assigned(FOnClientScreenShotGet) then
      // call it
      FOnClientScreenShotGet(FClientSender, LPngImage);
  finally
    Unlock;
    FreeAndNil(LStream);
    FreeAndNil(LPngImage);
    ClearBuffer(FTempBuffer);
  end; // tryf
end;

procedure TClientThread.DoClientScreenShotSend;
var
  LBuffer: TBytes;
  // screen shot holder
  LBitmap: TBitmap;
  // the protocol
  LProtocol: TProtocol;
  // in memory bytes stream
  LBytesStream: TBytesStream;
  // the png image, we assign LBitmap to LPngImage so we send less data
  LPngImage: TPngImage;
  // handle to the desktop canvas
  LDesktopCanvasHandle: HDC;
begin
  // fill protocol variable with zero's
  InitProtocol(LProtocol);
  // set the command
  LProtocol.Command := cmdScreenShotData;
  // set the sender
  LProtocol.Sender := FClient;
  // set the receiver, the client who requested the screen shot
  LProtocol.Receiver := FClientSender;
  // create object instances
  LBitmap := TBitmap.Create;
  LPngImage := TPngImage.Create;
  LBytesStream := TBytesStream.Create;
  Lock;
  try
    // get handle to desktop canvas
    LDesktopCanvasHandle := GetWindowDC(GetDesktopWindow);
    // set the bitmap height and width
    LBitmap.Height := Screen.Height;
    LBitmap.Width := Screen.Width;
    // copy the screen data from desktop to LBitmap
    BitBlt(
      LBitmap.Canvas.Handle,
      0, 0,
      Screen.Width, Screen.Height,
      LDesktopCanvasHandle,
      0, 0,
      SRCCOPY);
    // convert from bitmap to png image
    LPngImage.Assign(LBitmap);
    // save the png image to stream
    LPngImage.SaveToStream(LBytesStream);
    // set the data size in protocol structure
    LProtocol.DataSize := LBytesStream.Size;
    // convert protocol to array of bytes
    LBuffer := ProtocolToBytes(LProtocol);
    // increase the size of the buffer to  + 
    SetLength(LBuffer, szProtocol + LProtocol.DataSize);
    // move screen shot data from the stream to the buffer that we send
    Move(LBytesStream.Bytes[0], LBuffer[szProtocol], LProtocol.DataSize);
    // send buffer to the server
    FTCPClient.IOHandler.Write(LBuffer);
  finally
    Unlock;
    ClearBuffer(LBuffer);
    FreeAndNil(LBitmap);
    FreeAndNil(LPngImage);
    FreeAndNil(LBytesStream);
  end; // tryf
end;

procedure TClientThread.Execute;
var
  LBuffer: TBytes;
  LMessage: TBytes;
  LDataSize: Integer;
  LProtocol: TProtocol;
begin
  inherited;
  // while the thread is not terminated and the client is connected
  while NOT Terminated and FTCPClient.Connected do begin
    // store the size of the InputBuffer in LDataSize
    LDataSize := FTCPClient.IOHandler.InputBuffer.Size;
    // if we have some data in the InputBuffer, at least the size of the Protocol structure
    if LDataSize >= szProtocol then
      try
        // then read from InputBuffer the size of the protocol structure
        FTCPClient.IOHandler.ReadBytes(LBuffer, szProtocol);
        // convert array of bytes to protocol
        LProtocol := BytesToProtocol(LBuffer);
        // store the sender to private variable
        FClientSender := LProtocol.Sender;
        // check the command
        case LProtocol.Command of
          cmdConnect: begin
            // sync with main thread
            Synchronize(Self.DoClientConnect);
          end; // cmdConnect: begin
          cmdDisconnect: begin
            // sync with main thread
            Synchronize(Self.DoClientDisconnect);
          end; // cmdDisconnect: begin
          cmdMessageBroadcast, cmdMessagePrivate: begin
            // when we get a message after the protoocl we also get additional
            // data which is the message in this case
            // read the message data, the size of the message is in LProtocol.DataSize
            FTCPClient.IOHandler.ReadBytes(LMessage, LProtocol.DataSize);
            // decompress the message and store it in private variable
            FTempMessage := ZDecompressStr(LMessage);
            // sync with main thread
            Synchronize(Self.DoClientMessage);
          end; // cmdMessageBroadcast, cmdMessagePrivate: begin
          cmdScreenShotGet: begin
            // a client requested a screen shot
            // sync with main thread
            Synchronize(Self.DoClientScreenShotSend);
          end; // cmdScreenShotGet: begin
          cmdScreenShotData: begin
            // we received a screen shot on request
            // read the screen shot data
            FTCPClient.IOHandler.ReadBytes(FTempBuffer, LProtocol.DataSize);
            // sync with main thread
            Synchronize(Self.DoClientScreenShotGet);
          end; // cmdScreenShotData: begin
        end; // case LProtocol.Command of
      finally
        // clear buffer and message
        ClearBuffer(LBuffer);
        ClearBuffer(LMessage);
      end; // tryf
    // we needs to call Sleep so that this thread will not eat too much CPU
    // 50 miliseconds should be perfect
    // NOTE: we do NOT lose data, we just create a small latency
    Sleep(50);
  end; // while NOT Terminated and FTCPClient.Connected do begin
end;

procedure TClientThread.Lock;
begin
  FCriticalSection.Enter;
end;

procedure TClientThread.SendConnected;
var
  LBuffer: TBytes;
  LProtocol: TProtocol;
begin
  // fill protocol memory with zero's
  InitProtocol(LProtocol);
  // set the command
  LProtocol.Command := cmdConnect;
  // set the sender
  LProtocol.Sender := FClient;
  // convert protocol to array of bytes
  LBuffer := ProtocolToBytes(LProtocol);
  Lock;
  try
    // send command to server
    FTCPClient.IOHandler.Write(LBuffer);
  finally
    Unlock;
    ClearBuffer(LBuffer);
  end; // tryf
end;

procedure TClientThread.SendDisconnected;
var
  LBuffer: TBytes;
  LProtocol: TProtocol;
begin
  // fill protocol memory with zero's
  InitProtocol(LProtocol);
  // set the command
  LProtocol.Command := cmdDisconnect;
  // set the sender
  LProtocol.Sender := FClient;
  // convert protocol to array of bytes
  LBuffer := ProtocolToBytes(LProtocol);
  Lock;
  try
    // send command to server
    FTCPClient.IOHandler.Write(LBuffer);
  finally
    Unlock;
    ClearBuffer(LBuffer);
  end; // tryf
end;

procedure TClientThread.SendMessageBroadcast(const AMessage: string);
var
  LBuffer: TBytes;
  LProtocol: TProtocol;
  LMessage: TBytes;
begin
  // fill protocol memory with zero's
  InitProtocol(LProtocol);
  // compress the message
  LMessage := ZCompressStr(AMessage);
  // set the command
  LProtocol.Command := cmdMessageBroadcast;
  // set the sender
  LProtocol.Sender := FClient;
  // set the DataSize to the number of bytes that the message contains
  LProtocol.DataSize := Length(LMessage);
  // convert protocol to bytes
  LBuffer := ProtocolToBytes(LProtocol);
  // set the length of the buffer to  + 
  SetLength(LBuffer, szProtocol + LProtocol.DataSize);
  // move message to buffer
  Move(LMessage[0], LBuffer[szProtocol], LProtocol.DataSize);
  Lock;
  try
    // send message to server
    FTCPClient.IOHandler.Write(LBuffer);
  finally
    Unlock;
    ClearBuffer(LBuffer);
    ClearBuffer(LMessage);
  end; // tryf
end;

procedure TClientThread.SendMessagePrivate(const AReceiver: TClient;
  const AMessage: string);
var
  LBuffer: TBytes;
  LProtocol: TProtocol;
  LMessage: TBytes;
begin
  // fill protocol memory with zero's
  InitProtocol(LProtocol);
  // compress the message
  LMessage := ZCompressStr(AMessage);
  // set the command
  LProtocol.Command := cmdMessagePrivate;
  // set the sender
  LProtocol.Sender := FClient;
  // set the receiver
  LProtocol.Receiver := AReceiver;
  // set the DataSize to the number of bytes the message contains
  LProtocol.DataSize := Length(LMessage);
  // convert protocol to bytes
  LBuffer := ProtocolToBytes(LProtocol);
  // set the length of the buffer to  + 
  SetLength(LBuffer, szProtocol + LProtocol.DataSize);
  // move message to buffer
  Move(LMessage[0], LBuffer[szProtocol], LProtocol.DataSize);
  Lock;
  try
    // send message to server
    FTCPClient.IOHandler.Write(LBuffer);
  finally
    Unlock;
    ClearBuffer(LBuffer);
    ClearBuffer(LMessage);
  end; // tryf
end;

procedure TClientThread.SendScreenShotReq(const AReceiver: TClient);
var
  LBuffer: TBytes;
  LProtocol: TProtocol;
begin
  // fill protocol memory with zero's
  InitProtocol(LProtocol);
  // set the command
  LProtocol.Command := cmdScreenShotGet;
  // set the sender
  LProtocol.Sender := FClient;
  // set the receiver
  LProtocol.Receiver := AReceiver;
  // convert protocol to bytes
  LBuffer := ProtocolToBytes(LProtocol);
  Lock;
  try
    // send request to server
    FTCPClient.IOHandler.Write(LBuffer);
  finally
    Unlock;
    ClearBuffer(LBuffer);
  end; // tryf
end;

procedure TClientThread.Unlock;
begin
  FCriticalSection.Leave;
end;
In the "OnExecute" event of the TIdTCPServer component we need to handle client requests like so
procedure TfrmMain.ServerExecute(AContext: TIdContext);
var
  // temporary buffer
  LBuffer: TBytes;
  // temporary message buffer
  LMessageBuffer: TBytes;
  // data size in InputBuffer
  LDataSize: Integer;
  // protocol structure
  LProtocol: TProtocol;
  // we need to HARD CAST AContext to TClientContext
  // in order to access our custom methods(procedures)
  LClientContext: TClientContext;
begin
  // hard cast AContext to TClientContext
  LClientContext := TClientContext(AContext);
  // store the size of the InputBuffer of the client
  LDataSize := LClientContext.Connection.IOHandler.InputBuffer.Size;
  // in order to prevent spams or to make sure that we have at least
  // the protocol structure sent we check the size of the InputBuffer
  if LDataSize >= szProtocol then
    try
      // read the protocol structure from the client so we can handle
      // the client's request
      LClientContext.Connection.IOHandler.ReadBytes(LBuffer, szProtocol);
      // convert the buffer to protocol structure
      LProtocol := BytesToProtocol(LBuffer);
      // check client command and act accordingly
      case LProtocol.Command of
        cmdConnect: begin
          // the client just connected
          AddFmtLog(' %s', [LProtocol.Sender.UserName]);
          // set the client information in client context
          LClientContext.Client := LProtocol.Sender;
          // send the client list to this client so he knows who's connected
          LClientContext.SendClientList;
          // notify other clients that this client is connected
          LClientContext.BroadcastBuffer(LBuffer);
        end; // cmdConnect: begin
        cmdDisconnect: begin
          // client is disconnecting
          AddFmtLog(' %s', [LProtocol.Sender.UserName]);
          // notify other clients that this client is diconnecting
          LClientContext.BroadcastBuffer(LBuffer);
        end; // cmdDisconnect: begin
        cmdMessageBroadcast: begin
          // client is broadcasting a message
          // read the message from the sender client, the size of the mssages is
          // stored in DataSize member of the protocol structure
          LClientContext.Connection.IOHandler.ReadBytes(LBuffer, LProtocol.DataSize);
          // set the length of the temporary message buffer
          SetLength(LMessageBuffer, LProtocol.DataSize);
          // move the message data from the buffer to message buffer
          Move(LBuffer[szProtocol], LMessageBuffer[0], LProtocol.DataSize);
          AddFmtLog('<%s> %s', [
            LProtocol.Sender.UserName,
            ZDecompressStr(LMessageBuffer)]);
          // broadcast the message
          LClientContext.BroadcastBuffer(LBuffer);
        end; // cmdMessageBroadcast: begin
        cmdMessagePrivate: begin
          // client is sending a private message
          // read the message from the sender client
          LClientContext.Connection.IOHandler.ReadBytes(LBuffer, LProtocol.DataSize);
          // set the length of the temporary message buffer
          SetLength(LMessageBuffer, LProtocol.DataSize);
          // move the message data from the buffer to message buffer
          Move(LBuffer[szProtocol], LMessageBuffer[0], LProtocol.DataSize);
          AddFmtLog(' %s> %s', [
            LProtocol.Sender.UserName,
            LProtocol.Receiver.UserName,
            ZDecompressStr(LMessageBuffer)]);
          // send the message to the receiver client
          LClientContext.SendBuffer(LBuffer, LProtocol.Receiver.ID);
        end; // cmdMessagePrivate: begin
        cmdScreenShotGet: begin
          // client is requesting a screen shot
          AddFmtLog(' %s from %s', [
            LProtocol.Sender.UserName,
            LProtocol.Receiver.UserName]);
          // forward the request to target client
          LClientContext.SendBuffer(LBuffer, LProtocol.Receiver.ID);
        end; // cmdScreenShotGet: begin
        cmdScreenShotData: begin
          // client is sending screen shot data to the client that requested
          // read the screen shot data from the sender client
          LClientContext.Connection.IOHandler.ReadBytes(LBuffer, LProtocol.DataSize);
          AddFmtLog(' %s to %s', [
            (LProtocol.DataSize / 1024),
            LProtocol.Sender.UserName,
            LProtocol.Receiver.UserName]);
          // forward the screen shot data to the target client
          LClientContext.SendBuffer(LBuffer, LProtocol.Receiver.ID);
        end; // cmdScreenShotData: begin
      end; // case LProtocol.Command of
    finally
      ClearBuffer(LBuffer);
      ClearBuffer(LMessageBuffer);
    end; // tryf
end;
In the form's "OnCreate" event you needs to set the context class of the server to our custom TClientContext so that the server will create our client context instance when a client is connected
procedure TfrmMain.FormCreate(Sender: TObject);
begin
  Server.ContextClass := TClientContext;
end;
The above code is just a overview of the application and protocol implementation, if your curious to see the rest of the code and test the application then
- get the source code
- get the source code + binary
Any comments highly appreciated.

2 comments:

  1. Salut. Im primul rand, multumesc pentru acest cod, este exact ce cautam... adica un exemplu bun pentru Indy... dar asta depaseste asteptarile, doar ca as avea o rugaminte la tine daca ai putin timp liber... Ai putea sa ii adaugi si o functie pentru transfer de fisiere? Eu am un program care facea acelasi lucru ca si asta doar ca foloseste ScktComp si nu sunt prea multumit de el. Am vrut sa-l portez in Indy dar n-am reusit. Crezi ca ai putea sa ma ajuti cu o functie de transfer fisiere la codul tau? Ceva gen TransferFile(AClient, 'c:\filename'). Simplu, nimic mai complicat. Am nevoie de aceasta functie deoare ce am 3 calculatoare pe care le monitorizez dar uneori am nevoie si sa iau sau sa pun fisiere. Ti-as ramane dator daca ai putea sa ma ajuti ca m-ai scuti de drumuri cu stick-ul ;)) Mersi.

    ReplyDelete
    Replies
    1. salut, lasa mail-ul in comentariu si iti dau pe mail un program care face transfer de fisier si chat, ii bazat pe acest post.
      Mail-ul nu va fi facut pulic.

      Delete

Blogroll(General programming and Delphi feeds)