Dynamiczna tablica wątków i zawieszenie programu.

0

Mam oto taki kod serwera:

program Serwer;

{$mode objfpc}{$H+}

{------------------------------------------------------------------------------}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,blcksock,synsock,sysutils,Crt
  { you can add units after this };

{------------------------------------------------------------------------------}

{$R *.res}
Type
  TClient = class(TThread)
    private
      ClientSock : TTCPBlockSocket;
    protected
      procedure Execute; override;
   end;

{------------------------------------------------------------------------------}

Var
  Server           : TTCPBlockSocket;
  Client           : Array of TClient;
  Socket           : TSocket;
  ConnectedClients : LongInt;
  FreeClients      : LongInt;
  CanRead          : Boolean;
  Key              : Char;
  i                : LongInt;

{------------------------------------------------------------------------------}

Procedure TClient.Execute;
Var
  buff : String;
Begin
  FreeOnTerminate := True;
  ClientSock := TTCPBlockSocket.Create;
  ClientSock.Socket := Socket;
  Repeat
    buff := ClientSock.RecvString (1);
    If buff = 'connected' Then
      Writeln ('Podłączono ',ConnectedClients,' klientów')
    else If buff <> '' Then
      Writeln (buff);
  Until (buff = 'end') or (buff = 'END');
  ClientSock.CloseSocket;
  ConnectedClients := ConnectedClients - 1;
end;

{------------------------------------------------------------------------------}

Begin
  ClrScr;

  // Start serwera

  Write ('Inicjacja serwera...');
  Server := TTCPBlockSocket.Create;
  Server.Bind('127.0.0.1', '1234');
  If Server.LastError <> 0 Then
  Begin
    Write ('Błąd: ');
    Writeln (Server.LastErrorDesc);
    ReadKey;
    Halt (Server.LastError);
  end;
  ConnectedClients := 0;
  FreeClients := 0;
  Server.Listen;
  Writeln ('Zakończona');

  // Praca serwera

  Repeat
    CanRead := Server.CanRead (1);
    If CanRead Then
    Begin
      Socket := Server.Accept;
      ConnectedClients := ConnectedClients + 1;
      FreeClients := FreeClients + 1;
      SetLength (Client,FreeClients);
      Client[FreeClients-1] := TClient.Create (false);
    end;
    If KeyPressed Then Key := ReadKey;
  Until (Key = 'q') or (Key = 'Q');

  // Koniec pracy i zatrzymanie serwera

  For i := 0 To FreeClients-1 Do Client[i].Free;
  Server.CloseSocket;
end.

Napisałem takie coś bo uczę się jak zastosować wielowątkowość razem z komunikacją programów przez sieć. I z tego co zauważyłem to program owszem łączy się z wieloma klientami wyświetla przesłane do niego stringi oraz ilość podłączonych klientów ale gdy naciskam q aby go zakończyć to zawiesza się na pętli

For i := 0 To FreeClients-1 Do Client[i].Free;

Jeśli tą pętlę wywalę z programu to program zamyka się poprawnie a ponieważ jest to koniec programu to wątki poboczne też się zamykają więc w sumie nie jest ta pętla potrzebna ale mimo wszystko chciałbym wiedzieć czemu ona nie działa. Ponieważ będzie mi to potrzebne na zamykanie wątków klientów którzy się już odłączyli.

To raz a dwa w głównym wątku po podłączeniu klienta zmieniam wartość ConnectedClients o 1 w górę i ta wartość się zmienia.
W wątku pobocznym gdy on się kończy zmieniam tę wartość o 1 w dół i tutaj ta wartość już zmienić się nie chce. Wygląda na to że w wątkach pobocznych nie można zmieniać zmiennych globalnych bo nie nalezą one do wątku pobocznego tylko głównego i tu moje drugie pytanie. Jak modyfikować zmienne globalne z wątku pobocznego?

PS.
Jest to mój pierwszy program z wykorzystaniem wielowątkowości i dobrze wiem, że nie jest idealny, więc proszę nie wieszajcie na mnie psów. Każdy kiedyś zaczynał :)

0

zamień

 Until (buff = 'end') or (buff = 'END');

na

 Until (buff = 'end') or (buff = 'END') or Terminated;

a jeszcze lepiej na

 Until SameText(buff, 'end') or Terminated;

a to

For i := 0 To FreeClients-1 Do Client[i].Free;

na

For i := 0 To FreeClients-1 Do Client[i].Terminate;

Poza tym używanie zmiennych globalnych w stylu ConnectedClients i odwoływanie się do nich z wątku to niezbyt dobry pomysł :)

0
Misiekd napisał(a):

Poza tym używanie zmiennych globalnych w stylu ConnectedClients i odwoływanie się do nich z wątku to niezbyt dobry pomysł :)

Ale takie coś jest mi potrzebne by nie zaśmiecać pamięci bo chce zrobić , że jak klient się odłączy to jego wątek jest wywalany z pamięci i tablica jest zmniejszana o 1 a do tego jest mi potrzebna jakaś zmienna dostępna dla wątków pobocznych jak i dla wątku głównego. Doczytałem, że można to zrobić za pomocą synchronizacji ale nie wiem jak to napisać. Ktoś ogarnia synchronizację wątków i może mi to wytłumaczyć?

0

dzięki temu FreeOnTerminate := True; wątek jest AUTOMATYCZNIE zwalniany po zakończeniu i próba jego ręcznego zwolnienia to AV. A co do info jak się skończy to klasa TThread ma takie zdarzenie jak OnTerminate

0

To wiem ale wypadało by jeszcze zmniejszyć tablicę wątków która po zakończeniu wątku nie jest automatycznie zmniejszana i zdarzenie OnTerminate się przyda ale wypadało by też wiedzieć który wątek się skończył a do tego jest już mi potrzebne zapisanie tej informacji do zmiennej należącej do wątku głównego.

0

ConnectedClients := ConnectedClients + 1;
FreeClients := FreeClients + 1;
SetLength (Client,FreeClients);
Client[FreeClients-1] := TClient.Create (false);

Tak się tego nie robi. Masz high(client) i length(client), z tego korzystaj!

Jak modyfikować zmienne globalne z wątku pobocznego?

W Lazarus wiki jest coś takiego jak 'Multithreaded application tutorial'. Szukaj zamiast pytać.

Jest to mój pierwszy program z wykorzystaniem wielowątkowości i dobrze wiem, że nie jest idealny, więc proszę nie wieszajcie na mnie psów.

Nie martw się, już jesteś ponad przeciętny... Bo pytanie zadałeś.. :)

Wygląda na to że w wątkach pobocznych nie można zmieniać zmiennych globalnych bo nie nalezą one do wątku pobocznego tylko głównego

Ja robię tak, że w głównym wątku sprawdzam czy któryś zgłosił zakończenie i wtedy go zwalniam. Może nie idealne ale działa dobrze.

Doczytałem, że można to zrobić za pomocą synchronizacji ale nie wiem jak to napisać. Ktoś ogarnia synchronizację wątków i może mi to wytłumaczyć?

Doczytaj jeszcze, naprawdę, skoro ja oganiam wątki a nie pytałem to znaczy że to jest w googlach i można to znaleźć,

Pamiętaj: Wątki są wyjątkowo niemiłe, powodują masę problemów. Eksperymentuj, szukaj, tylko w ostateczności pytaj. Dzięki temu nauczysz się polegać na sobie i rozwiązywać te problemy całkiem skutecznie.

0
-oho123 napisał(a):

Tak się tego nie robi. Masz high(client) i length(client), z tego korzystaj!

Nie wiem co to ale poczytam.

-oho123 napisał(a):

W Lazarus wiki jest coś takiego jak 'Multithreaded application tutorial'. Szukaj zamiast pytać.

Znalazłem i napisałem takie coś:

program Serwer;

{$mode objfpc}{$H+}

{------------------------------------------------------------------------------}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,blcksock,synsock,sysutils,Crt;

{------------------------------------------------------------------------------}

{$R *.res}
Type
  TClient = class(TThread)
    private
      ClientSock : TTCPBlockSocket;
      Procedure DecClients;
    protected
      procedure Execute; override;
   end;

{------------------------------------------------------------------------------}

Type
  TServer = class
    public
      Server           : TTCPBlockSocket;
      Client           : Array of TClient;
      Socket           : TSocket;
      ConnectedClients : LongInt;
      FreeClients      : LongInt;
      CanRead          : Boolean;
      Key              : Char;

      Procedure Run;
  end;

{------------------------------------------------------------------------------}

Var
  OServer : TServer;

{------------------------------------------------------------------------------}

Procedure TClient.Execute;
Var
  buff : String;
Begin
  FreeOnTerminate := True;
  ClientSock := TTCPBlockSocket.Create;
  ClientSock.Socket := OServer.Socket;
  Repeat
    buff := ClientSock.RecvString (1);
    If buff = 'connected' Then
      Writeln (OServer.ConnectedClients,' Clients connected')
    else If buff <> '' Then
      Writeln (buff);
  Until SameText(buff, 'end') or Terminated;;
  ClientSock.CloseSocket;
  Synchronize (@DecClients);
end;

{------------------------------------------------------------------------------}

Procedure TClient.DecClients;
Begin
  OServer.ConnectedClients := OServer.ConnectedClients - 1;
end;

{------------------------------------------------------------------------------}

Procedure TServer.Run;
Var
  i : LongInt;
Begin
  ClrScr;

  // Start serwera

  Write ('Initializing server...');
  Server := TTCPBlockSocket.Create; // <-- Na tej linii wywala błąd.
  Server.Bind('127.0.0.1', '1234');
  If Server.LastError <> 0 Then
  Begin
    Write ('Error: ');
    Writeln (Server.LastErrorDesc);
    ReadKey;
    Halt (Server.LastError);
  end;
  ConnectedClients := 0;
  FreeClients := 0;
  Server.Listen;
  Writeln ('DONE!');

  // Praca serwera

  Repeat
    CanRead := Server.CanRead (1);
    If CanRead Then
    Begin
      Socket := Server.Accept;
      ConnectedClients := ConnectedClients + 1;
      FreeClients := FreeClients + 1;
      SetLength (Client,FreeClients);
      Client[FreeClients-1] := TClient.Create (false);
    end;
    If KeyPressed Then Key := ReadKey;
  Until (Key = 'q') or (Key = 'Q');

  // Koniec pracy i zatrzymanie serwera
  For i := 0 To FreeClients-1 Do Client[i].Terminate;
  Server.CloseSocket;
end;

{------------------------------------------------------------------------------}

Begin
  OServer.Run;
end.

Program się kompiluje jednak po uruchomieniu wywala takie coś: Project Serwer.exe raised exception class 'External: SIGSEGV'. Czemu? czyżby try.. except mogło pomóc?

0

a kto stworzy instancję klasy TServer?

0

uh to muszę konstruktor zrobić? To jest moj pierwszy program w lazarusie i klas nie ogarniam W Free Pascal IDE wystarczyło zrobić

 
Type
  costam = object
    zmienna1 : string;
    zmienna2 : integer;

    procedure RobCos;
end;

i to wystarczało. Chyba muszę poczytać o lazarusie więcej.

Dobra Poprawiona wersja programu. Kompiluje się, uruchamia, zastosowałem TThread.Synchronize, a zmienna ConnectedClients dalej tylko się zwiększa przy podłączeniu klienta a już nie chce się zmniejszyć przy jego odłączeniu.....

Kod:

program Serwer;

{$mode objfpc}{$H+}

{------------------------------------------------------------------------------}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes,blcksock,synsock,sysutils,Crt;

{------------------------------------------------------------------------------}

{$R *.res}
Type
  TClient = class(TThread)
    private
      ClientSock : TTCPBlockSocket;
      Procedure DecClients;
    protected
      procedure Execute; override;
   end;

{------------------------------------------------------------------------------}

Type
  TServer = class
    public
      Server           : TTCPBlockSocket;
      Client           : Array of TClient;
      Socket           : TSocket;
      ConnectedClients : LongInt;
      FreeClients      : LongInt;
      CanRead          : Boolean;
      Key              : Char;

      Constructor Create;
      Procedure Run;
      Destructor Stop;
  end;

{------------------------------------------------------------------------------}

Var
  OServer : TServer;

{------------------------------------------------------------------------------}

Procedure TClient.Execute;
Var
  buff : String;
Begin
  FreeOnTerminate := True;
  ClientSock := TTCPBlockSocket.Create;
  ClientSock.Socket := OServer.Socket;
  Repeat
    buff := ClientSock.RecvString (1);
    If buff = 'connected' Then
      Writeln (OServer.ConnectedClients,' Clients connected')
    else If buff <> '' Then
      Writeln (buff);
  Until SameText(buff, 'end') or Terminated;;
  ClientSock.CloseSocket;
  Synchronize (@DecClients);
end;

{------------------------------------------------------------------------------}

Procedure TClient.DecClients;
Begin
  OServer.ConnectedClients := OServer.ConnectedClients - 1;
end;

{------------------------------------------------------------------------------}

Constructor TServer.Create;
Begin
   ClrScr;

  // Start serwera

  Write ('Initializing server...');
  Server := TTCPBlockSocket.Create;
  Server.Bind('127.0.0.1', '1234');
  If Server.LastError <> 0 Then
  Begin
    Write ('Error: ');
    Writeln (Server.LastErrorDesc);
    ReadKey;
    Halt (Server.LastError);
  end;
  ConnectedClients := 0;
  FreeClients := 0;
  Server.Listen;
  Writeln ('DONE!');
end;

{------------------------------------------------------------------------------}

Procedure TServer.Run;
Begin
  // Praca serwera

  Repeat
    CanRead := Server.CanRead (1);
    If CanRead Then
    Begin
      Socket := Server.Accept;
      ConnectedClients := ConnectedClients + 1;
      FreeClients := FreeClients + 1;
      SetLength (Client,FreeClients);
      Client[FreeClients-1] := TClient.Create (false);
    end;
    If KeyPressed Then Key := ReadKey;
  Until (Key = 'q') or (Key = 'Q');
end;

{------------------------------------------------------------------------------}

Destructor TServer.Stop;
Var
  i : LongInt;
Begin
  // Koniec pracy i zatrzymanie serwera

  For i := 0 To FreeClients-1 Do Client[i].Terminate;
  Server.CloseSocket;
end;

{------------------------------------------------------------------------------}

Begin
  OServer := TServer.Create;
  OServer.Run;
  OServer.Stop;
end.
0

Podbijam temat. Ktoś może mi pomóc?

0

i to wystarczało. Chyba muszę poczytać o lazarusie więcej.

Zrozum że lazarus to FPC tylko że z dodatkami... Lazarus używa FPC z odpowiednimi parametrami, żadnej magii nie ma..
Twój problem polega na tym że nie rozróżniasz 'object' od 'class'. Object jest inicjalizowane automatycznie i nie potrzebuje wykonania konstruktora (albo jest wykonywany sam? nie jestem pewien). Poczytaj na wiki lazarusa...

Dobra Poprawiona wersja programu. Kompiluje się, uruchamia, zastosowałem TThread.Synchronize, a zmienna ConnectedClients dalej tylko się zwiększa przy podłączeniu klienta a już nie chce się zmniejszyć przy jego odłączeniu.....

A czemu miałoby?! Przecież nie masz w destruktorze nic.

Moja propozycja: Naucz się klas, wtedy wątków i zacznij od PODSTAW TEGO. Nie chodzi o żadne synchronize, tylko sekcje kryczyne/semafory... I znowu: lazarus wiki.
I jeżeli nikt nie odpowiada to znaczy że twoje pytanie nie jest warte odpowiedzi, bo ty nas traktujesz jak rozwiązywaczy twoich problemów.

0
-oho123 napisał(a):

A czemu miałoby?! Przecież nie masz w destruktorze nic.

Może dlatego że jest taka procedura jak DecClients? Ale ok przeczytam najpierw potem będę kombinował a jak nie wyjdzie to dopiero napisze.

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