How to detect other programs running on a network

I've developed an application which needs to check if other instances of it are running elsewhere on a network.

i.e. it needs to broadcast a message and collect the IP address of any other running instance. These instances would then work cooperatively to process a pool of "work" which could conceivably be extremely large. They would also need to pass status messages to each other.

Does anyone know how to do this (both the collection of IP address and the broadcast of messages to these IP addresses). I realise I could control this to an extent at a database level (i.e.which clients are active), but I don't want the broadcast of messages to depend on other instances periodically looking at the database - I need it to be as fast as possible.

Thanks for any help

Steve
steve-westAsked:
Who is Participating?
 
CodedKConnect With a Mentor Commented:
Hi Steve.
I've made an example for you. I hope this is what you want. :)
It enumerates the names / IPs of all Network machines and then enumerates running processes
and then check if your application is running...

You need to add "WbemScripting_TLB" and "ActiveX" in uses clause among other stuff... To add
"WbemScripting_TLB" you have to import this type library:
Microsoft WMI Scripting v1.x Library (Version 1.x)
From Project -> Import type library
~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-
Here is the code :


unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WinSock, ActiveX, WbemScripting_TLB;
 
type
  TForm1 = class(TForm)
    Button1: TButton;
    ComboBox1: TComboBox;
    Button2: TButton;
    ListBox1: TListBox;
    Label1: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  procedure NetErrorHandler( dwResult  : DWORD; const Msg : String);
  procedure DisplayStruct(lpnr : PNETRESOURCE);
  function EnumerateFunc(lpnr : PNETRESOURCE) : Boolean;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
function GetAddressByName(name : string) : string;
var
wsdata:TWSAData;
he: PHostEnt;
ss: pchar;
ip: TInAddr;
begin
     WSAStartup(MakeWord(1, 1), wsdata);
  he := gethostbyname(pchar(name));
  if he<>nil then
  begin
       ip.S_addr := integer(pointer(he^. h_addr_list^)^);
     ss := inet_ntoa(ip);
     Result := string(ss);
  end;
  WSACleanup();
end;
 
 
procedure TForm1.NetErrorHandler(
   dwResult  : DWORD;
   const Msg : String);
begin
    Application.MessageBox
    (PChar(Msg + ' Error #' + IntToStr(dwResult)),'Error', MB_OK);
end;
 
procedure TForm1.DisplayStruct(lpnr : PNETRESOURCE);
var
    p : PChar;
begin
  p := lpnr^.lpRemoteName;
  if (p[0] = '\') and (p[1] = '\') then p := p + 2;
  Combobox1.Items.Add(GetAddressbyName(p));
end;
 
function TForm1.EnumerateFunc(lpnr : PNETRESOURCE) : Boolean;
var
    dwResult     : DWORD;
    dwResultEnum : DWORD;
    hEnum        : THandle;
    cbBuffer     : DWORD;             // 16K is a good size
    cEntries     : DWORD;             // enumerate all possible entries
    lpnrLocal    : PNETRESOURCE;      // pointer to enumerated structures
    PtrResource  : PNetResource;
    i            : DWORD;
begin
    cbBuffer := 16384;
    cEntries := DWORD(-1);
    if Application.Terminated then begin
        Result := FALSE;
        Exit;
    end;
 
    dwResult := WNetOpenEnum(RESOURCE_GLOBALNET,
                             RESOURCETYPE_ANY,
                             RESOURCEUSAGE_CONTAINER, // enumerate allresources
                             lpnr,                    // NULL first timethis function is called
                             hEnum);                  // handle to resource
 
    if dwResult <> NO_ERROR then begin
        NetErrorHandler
        (dwResult, 'Not connected ?');
        Result := FALSE;
        Exit;
    end;
 
    repeat
        // Allocate memory for NETRESOURCE structures.
        GetMem(lpnrLocal, cbBuffer);
 
        dwResultEnum := WNetEnumResource(hEnum,       // resource handle
                                         cEntries,    // defined locally as0xFFFFFFFF
                                         lpnrLocal,   // LPNETRESOURCE
                                         cbBuffer);   // buffer size
 
        if dwResultEnum = NO_ERROR then begin
            for i := 0 to cEntries - 1 do begin
                PtrResource := PNETRESOURCE(PChar(lpnrLocal) + i *SizeOf(lpnrLocal^));
                if PtrResource^.dwDisplayType = RESOURCEDISPLAYTYPE_SERVER then
                    DisplayStruct(PtrResource);
                Application.ProcessMessages;
                // If this NETRESOURCE is a container, call the function
                // recursively.
                if (RESOURCEUSAGE_CONTAINER = (PtrResource^.dwUsage and RESOURCEUSAGE_CONTAINER)) and
                   (PtrResource^.dwDisplayType <>RESOURCEDISPLAYTYPE_SERVER) then begin
                    if (not EnumerateFunc(PtrResource)) then begin
                         TextOut(Application.Handle, 10, 10, 'EnumerateFunc returned FALSE.',29);
                    end;
                end;
            end;
        end
        else if dwResultEnum <> ERROR_NO_MORE_ITEMS then begin
            NetErrorHandler(dwResultEnum, 'Cant find Net Resources !');
            break;
        end;
    until dwResultEnum = ERROR_NO_MORE_ITEMS;
 
    FreeMem(lpnrLocal);
 
    dwResult := WNetCloseEnum(hEnum);
 
    if dwResult <> NO_ERROR then begin
        NetErrorHandler(dwResult, 'An error occured !');
        Result := FALSE;
        Exit;
    end;
 
    Result := TRUE;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
   EnumerateFunc(nil);
end;
 
 
//----------
function ADsEnumerateNext (pEnumVariant: IEnumVARIANT;
  cElements: ULONG; var pvar: OleVARIANT;
  var pcElementsFetched: ULONG): HRESULT;
  safecall; external 'activeds.dll';
 
 
procedure DumpWMI_Process(Process: SWBemObject; sList: TStrings);
var
  Enum: IEnumVARIANT;
  varArr: OleVariant;
  lNumElements: ULong;
  SProp: ISWbemProperty;
  Prop: OleVariant;
  PropName: string;
  PropType: string;
  PropValue: string;
begin
  Enum := Process.Properties_._NewEnum as IEnumVariant;
  while (Succeeded(ADsEnumerateNext(Enum, 1, VarArr, lNumElements))) and (lNumElements > 0) do begin
    if Succeeded(IDispatch(varArr).QueryInterface(SWBemProperty, SProp)) and Assigned(SProp) then begin
      try
        PropName := SProp.Name;
        Prop := SProp.Get_Value;
        PropType := IntToStr(VarType(Prop));
        PropValue := VarToStr(Prop);
        if PropName = 'Name' then
          sList.Add(UpperCase(PropValue));
      except on E: Exception do sList.Add('Error ' + PropName + ': ' + E.Message);
      end;
    end;
  end;
end;
 
 
 
procedure EnumProcessesOnComputer(Server: string; sList: TStrings);
var
  Enum: IEnumVARIANT;
  varArr: OleVariant;
  lNumElements: ULong;
begin
    try
      Enum := CoSWbemLocator.Create.ConnectServer(Server, 'root\cimv2', '', '', '', '', 0, nil).ExecQuery('Select * from Win32_Process', 'WQL', wbemFlagBidirectional, nil)._NewEnum as IEnumVariant;
      while (Succeeded(ADsEnumerateNext(Enum, 1, varArr, lNumElements))) and (lNumElements > 0) do
      begin
        DumpWMI_Process(IUnknown(varArr) as SWBemObject, sList);
      end;
    except on E: Exception do sList.Add(E.Message);
    end;
end;
 
 
procedure TForm1.Button2Click(Sender: TObject);
var
  sl: TStringList;
  i: Integer;
begin
  Screen.Cursor := crHourglass;
  ListBox1.Clear;
  sl := TStringList.Create;
  try
    EnumProcessesOnComputer(Label1.Caption, sl);
    Screen.Cursor := crDefault;
 
    For i:=0 to Pred(SL.Count) do
    begin
      ListBox1.Items.Add(SL.Strings[i]);
    end;
  finally
    //sl.Free;
    Screen.Cursor := crDefault;
  end;
 
  if sl.IndexOf('firefox.exe') > -1 then
    ShowMessage('FireFox is running in remote machine '+Label1.Caption)
  else
    ShowMessage('FireFox is NOT running in remote machine '+Label1.Caption);  // Or ip if you want
end;
 
procedure TForm1.ComboBox1Change(Sender: TObject);
var
    Host      : PHostEnt;
    myWSAData : TWSAData;
    myVerReq  : Word;
    PIx       : u_long;
begin
 If ComboBox1.Text='Destination' then Button2.Enabled:=False
 else
 Begin
    Button2.Enabled:=True;
    myVerReq := $0101;
    if WSAStartUp(myVerReq, myWSAData) <> 0 then exit ;
    PIx := Inet_Addr(pchar(ComboBox1.Text));
    Host := GetHostByAddr(addr(PIx) , 4, PF_INET);
    if Host <> nil then
    Begin
          Try
           Label1.Caption:=Host.h_name;
           Form1.Caption:='Selected User : '+Host.h_name;
 
          {If the string end in ".lan" (Ex. PC11.lan) then cut it...!}
           If Copy(Label1.Caption,Label1.GetTextLen-3,4)='.lan' then
              Label1.Caption:=Copy(Label1.Caption,0,Label1.GetTextLen-4);
           ComboBox1.Hint :=Label1.Caption;
 
          Except on E: Exception do ShowMessage
           ('Error occured ! Could not get the name of the user');
          end;
    End;
  End;
end;
 
end.

Open in new window

0
 
developmentguruPresidentCommented:
Given the nature of what you are working on It may work better to have a central server that acts as the forman for the other machines.  When the client program is run it would log into the server.  All communications would move through the server.  This would give you a centralized location for distributing work load and handling communications.  Just a thought.
0
 
CodedKCommented:
0
Hire Technology Freelancers with Gigs

Work with freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely, and get projects done right.

 
steve-westAuthor Commented:
Thanks Guys for these responses.

Development Guru : You have a good point regarding the managing the communication of these applications. This is what I do at the moment (but only on the level of local PC) - I have an invisible COM object that manages communication between several running applications. An application publishes a " change" with the COM object and this distributes it to each of its clients via eventsink interfaces.

However, I want this application to be as simple to manage and install as possible, without there being a "Key" application running. By implementing the messaging on a distributed basis, it doesn't matter if one machine - or several - aren't running, the other applications will adopt the workload. If the PC on which the Key application runs, if this goes down then all applications are going to suffer.

CodedK: That's for this, very interesting. I didn't realise that was possible. However, this only solves  half the problem

One issue with your example is speed. There could by hundreds of clients connected to the network - to have to go through all of them, enumerating the processes of each one to identify the running application would take absolutely ages. On our network here, with just a couple of dozen clients, it took almost 5 minutes.

Is it not possible to just send a "broadcast" on a particular port. All clients would "listen" to this port and if they detect such a broadcast, they could respond accordingly.

My question I suppose, is how to do this broadcast.


Anyway, thanks again for your responses.

Steve






0
 
developmentguruConnect With a Mentor PresidentCommented:
The problem with a broadcast is... what is your audience?  If you try to broadcast over the internet, how many systems might you be broadcasting to?  I would need to know more about the possible scale to try to find a best approach.  Some applications may have tens of thousands or even millions of applications connecting to them.

I fyou still think the broadcast approach works, here is a snippet from Borland showing how to do it with Indy controls via UDP.

http://delphi.newswhat.com/geoxml/forumhistorythread?groupname=borland.public.kylix.internet.sockets&messageid=3EADA194.3050104@prodigy.net.mx
0
 
steve-westAuthor Commented:
>>The problem with a broadcast is... what is your audience?  If you try to broadcast over the internet, how many systems might you be broadcasting to?.

Good point well made.

I've rethought my approach to this and have decided to have it all controlled by the database. A client connects, registers its IP address with the Database and removes it when it closes. The IP address is refreshed periodically by the client just in case the client crashes. At the time of refresh, all stale IP addresses are removed.

Thanks for pointing the above out to me and for the example

regards

Steve
0
All Courses

From novice to tech pro — start learning today.