[INDY] Klient-Serwer (konkretny problem)

0

Mam dwie apliakcje, serwer i klient, zbudowane w oparciu o komponenty IdTcpClient i IdTcpServer, gdy na serwerze wcisnę buttona to na kliencie ma zostać wykonana konkretna funkcja (w przykładzie nazwa funkcji to przykladowa_funkcja ;] )

SERWER:

var 
   funkcja_request : boolean = false;

//przycisk z żądaniem aby na kliencie została wykonana funkcja
procedure TForm1.Button2Click(Sender: TObject);
begin
 funkcja_request := true;
end;

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var
   pakiet : string;
Begin
  pakiet := AThread.Connection.ReadLn;
   
   if (pakiet = 'i') and (procedura_request = true) then
    begin
      AThread.Connection.WriteLn('call');
      funkcja_request := false;
    end;
end;

KLIENT:

procedure TForm1.FormActivate(Sender: TObject);
begin
    try
      local := true;
      IdTcpClient1.Host := 192.168.0.1 ; //przykładowy adres serwera
      IdTCPClient1.Connect;
    except
      ;    
    end;
end;

// komponent timer ustawiony interwał czasowy na 7000, co jakiś czas sprawdza czy na serwerze zarządano wykonania funkcji 

procedure TForm1.Timer2Timer(Sender: TObject);
var 
   pakiet_klient : string;
Begin 
 if (IdTCPClient1.Connected = true) then
 begin
   pakiet_klient := 'i';
   IdTCPClient1.WriteLn(pakiet_klient);

   pakiet_klient := IdTCPClient1.ReadLn;

   if pakiet_klient = 'call' then
   begin
     IdTCPClient1.Disconnect;
     przykladowa_funkcja;
   end;

  end;
end;

Problem: Wszystko działa dobrze, ale tylko w połaczeniu z jednym klientem (funkcja wykona się na jednym kliencie, na reszcie aplikacji klienckich nie (dopiero po zamknięciu klienta na którym funkcja została uruchomiona itd.)), męczę sie z tym ponad miesiąc i nic [glowa]

będę wdzięczny za każdą konstruktywną pomoc [browar] / [soczek]

0

Nie pamiętam dokładnie, co trzeba zrobić, bo w INDY nie siedziałem już od bardzo dawna. Musisz skorzystać z kolejki klientów (idTCPServer ma jakiegoś stringlista z numerami IP wszystkich podłączonych klientów) i w pętli wysłać wiadomość do każdego z nich po kolei.

0

OK, w związku z brakiem możliwości edycji (warto konto założyć co nie ; ] ) umieszczam poprawiony i bardziej jasny kod (aczkolwiek nadal efekt jest bardzo niezadawalający, klient napisany jest raczej dobrze, gorzej z serwerem, który nie obsługuje wielu klientów) co do wątków, kombinowałem z AThread.Destroy (w zdarzeniu OnExecute), wówczas rzeczywiście na każdym kliencie wyświetli się komunikat, ale potem taki klient się sypie.

KLIENT

//porty poustawiane na kliencie i serwerze wcześniej, serwer automatycznie włączony na nasłuch
//łączenie z serwerem
procedure TForm1.FormActivate(Sender: TObject);
begin
    try
      local := true;
      IdTcpClient1.Host := 192.168.0.1 ; //przykładowy adres serwera
      IdTCPClient1.Connect;
    except
      ;   
    end;
end;

//komponent Timer - co 2 sekundy sprawdź czy serwer nie wydał jakiegoś "polecenia"
procedure TForm1.Timer2Timer(Sender: TObject);
var
  pakiet_klient : string;
Begin
  if (IdTCPClient1.Connected = true) then
   begin
     pakiet_klient := IdTCPClient1.ReadLn; //nasłuchiowanie "rozkazów" z serwera

     if pakiet_klient = 'call' then
      begin
         showmessage('Pozdrowienia z gorącej serwerowni');
      end;
   end;
end;

SERWER

var
   request : boolean = false;

//przycisk z żądaniem aby na wszystkich klientach wyświetlić komunikat
procedure TForm1.Button2Click(Sender: TObject);
begin
 request := true;
end;

procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
Begin
  if procedura_request = true then
  begin
   AThread.Connection.WriteLn('call'); //wysłanie żadania  
   request := false;
  end;
end;

Ogólnie założenie jest takie - po połączeniu klienta do serwera, klienci cały czas nasłuchują "poleceń" wydanych z serwera, jeżeli serwer wyda "komunikat" o okreslonej treści wówczas klient wykona coś tam (w przykładzie po naciśnięciu buttona na serwerze na wszystkich klientach ma pojawić się komunikat), próbuje próbuje i ciągle się krzaczy (potrzebuje zaimplikować prostą komunikację sieciową w moim projekcie).

PS. Czy naprawdę nikt nie używa Indy (numer 9) do komunikacji sieciowej ;)
Pozdrawiam.

0

Ok. Specjalnie dla Ciebie zainstalowałem INDY :]

Wrzuć na forme serwera komponent IdThreadMgr z palety INDY.

Poźniej zadeklaruj klasę TConnection (połączenie) i TPackage (dane wiadomości wysłanej przez klienta):

type

  WConnection = ^TConnection;
  TConnection = record
    Host        : String[20];
    Thread      : Pointer;
  end;

  TPackage = record
   UserNick,
   Txt : string[255];
  end;

Zadeklaruj globalną zmienną:

var
  ConnectionLst: TThreadList;

Przy OnCreate formy serwera należy dać:

procedure TForm1.FormCreate(Sender: TObject);
begin
ConnectionLst := TThreadList.Create;
end;

Procedura przy przyłączeniu się klienta:

procedure TForm1.TCPServerConnect(AThread: TIdPeerThread);
var
NewConnection: WConnection;
begin
  GetMem(NewConnection, SizeOf(TConnection));

  // Pobieranie danych o połączaniu
  NewConnection.Host:=AThread.Connection.LocalName; 
  NewConnection.Thread:=AThread;
  AThread.Data:=TObject(NewConnection);

  try
  // dodanie połączenia do listy klientów
    ConnectionLst.LockList.Add(NewConnection);

  finally
    ConnectionLst.UnlockList;

  end;

end;

To jest procedura wykonywana przy wysłaniu wiadomości przez klienta, serwer odpisuje do każdego klienta wiadomość tego pierwszego. Przerób to po swojemu.

procedure TForm1.TCPServerExecute(AThread: TIdPeerThread);
var
  DestThread : TIdPeerThread;
  i : Integer;
  DestConnection : WConnection;
  Package, NewPackage : TPackage; // dane wiadomości wysłanej przez klienta
  
begin

    AThread.Connection.ReadBuffer (Package, SizeOf (Package));

        NewPackage := Package;

        with ConnectionLst.LockList do // wysyłamy do każdego klienta po kolei
        try
          for i := 0 to Count-1 do
        begin
            DestConnection := Items[i];
            DestThread := DestConnection.Thread;
            DestThread.Connection.WriteBuffer(NewPackage, SizeOf(NewPackage), True);
          end;
        finally
          ConnectionLst.UnlockList;
        end;
      end;
 end;

Formalność, procedura przy odłączeniu klienta - usunięcie klienta z listy połączeń

procedure TForm1.IdTCPServer1Disconnect(AThread: TIdPeerThread);
var
  ActiveConnection: WConnection;

begin
  ActiveConnection := WConnection(AThread.Data);
  try
    ConnectionLst.LockList.Remove(ActiveConnection);
  finally
    ConnectionLst.UnlockList;
  end;
  FreeMem(ActiveConnection);
  AThread.Data := nil;
end;

end.

Przepraszam, jeśli są błędy, musiałem się śpieszyć bo obiad czeka na stole :P

0

Dzięki za kod ale ...

wszystko byłoby pięknie gdyby podczas rozłączenia klienta z serwerem (przy rozłączaniu IdTCpClent.Disconnet klienta w zdarzeniu OnClose) klient nie bombardował komunikatami o treści: "Connection Closed Gracefully" (ilość komunikatów uzależniona od ilości uruchomionych kklientów oraz czasu połaczenia z serwerem") - dopiero po przejściu całej serii komuniaktów klient zamyka się, hmm jaka przyczyna ?</delphi>

0

Dobra już z grubsza wiem o co chodzi z tym komunikatem (sorry za zamieszanie, najpierw pomyśl, potem napisz ;] - proszę moderatora o usunięcia postu powyżej)

Konikowi jeszcze raz bardzo dziękuje za pomoc (proponuję autorowi podpiąc ten kodzik do gotowców, artykułów (dodać do artykułu o Indy) lub Faq :] )

1 użytkowników online, w tym zalogowanych: 0, gości: 1