Ćwiczenie z TCP/IP — przesłanie linijki tekstu pomiędzy serwerem i clientem

0

Dobry wieczór,
Zacząłem ćwiczyć tcp ip z Synapse. Oczywiście nieporadnie.
Czy możecie poprawić mój - dla Was zapewne najbanalniejszy kod tak aby potrafił przesłać linijkę tekstu pomiędzy serwerem i clientem i odwrotnie? jaki błąd robię? Szukałem synapse w dokumentacji ale kod jest bardzo długi a nie chcę go przeklejać tylko na najprostszym przykładzie zrozumieć. Serwer i klient mają cały czas sprawdzać i nasłuchiwać wartość zmiennej globalne wyślij:string, a odpowiedzi otrzymywać w globalnej zmiennej przyjmij:string. Server i Klient jest u mnie umieszczony w wątkach ma nie blokować kiedyś wątku głównego. Próbowałem zarówno w dwóch programach(serwer i klient osobno) jak i jednym jako jednocześnie działające. Podczas wysyłki klientem program się zamyka/ jakiś wielki błąd się robi. funkcja getip odczytuje IP komputera aktualny. Może macie link do najprostszych przykładów użycia synapse w tcp ip bez na razie zaawansowanego programowania.

procedure tclient.execute;   // wątek klienta
label 1;
begin
c := TTCPBlockSocket.Create;
c.Connect (ip, port);
1:
if wyslij<>'' then begin
c.SendString (wyslij);
przyjmij := c.RecvString(timeout);
wyslij:=''; //po wysłaniu ma się skasować aby nie wysyłało co 100ms
end;
sleep(100);
goto 1;
end;

procedure tserver.execute;   //wątek serwera
label 1;
var
reciv:string;
begin
s := TTCPBlockSocket.Create;
s.Bind (ip, port);
1:
s.Listen;
if s.canread (timeout) then
begin
  przyjmij:=s.RecvString(timeout);
  s.SendString(wyslij); // wyslij np = 'ok! odebrano';
end;
sleep(100);
goto 1;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
ser:tserver;
cli:tclient;
begin
port:='8008';
timeout:=1000;
ip:=getip;
cli:=tclient.Create(false); // mają działac cały czas słuchając i być gotowe do zmian zmiennych globalnych
ser:=tserver.Create(false);
end;
2

Najpierw wypadałoby sformatować ten kod i zastosować się do przyjętej konwencji nazewnictwa oraz stylu pisania kodu — piszesz kod byle jak, a takiego nie da się czytać. Przydatny będzie jak zwykle artykuł Object Pascal Style Guide — przeczytaj go, zobacz jak powinno się pisać zwięzły i czytelny kod.

Główny problem Twoich wątków to to, że ich metody Execute nigdy się nie skończą — masz nieskończone pętle, bez żadnych warunków. Typowa struktura metody Execute wygląda tak:

procedure TClient.Execute();
begin
  while not Terminated do // jeśli nie przerwano pracy wątku
  begin
    // wykonuj instrukcje
    // ewentualny "Sleep"
  end;
end;

U Ciebie jest nieskończona pętla, a więc wykonanie metody Terminate nic nie da — ten będzie działał dalej.

Drugi problem jest taki, że zasoby tworzysz w metodzie Execute, a powinieneś w konstruktorze klasy wątku — mowa tutaj o TTCPBlockSocket. Trzeci problem jest taki, że te wątki operują na globalnych zmiennych łańcuchowych bez żadnej synchronizacji, a to nie ma prawa działać prawidłowo i na pewno zdarzy się sytuacja, w której jeden wątek będzie modyfikował zawartość zmiennej a inny z niej odczytywał i poleci wyjątek.

Najprostszym sposobem synchronizacji pracy wątków jest metoda Synchronize, która wywołuje daną procedurę w ramach głównego wątku, dzięki czemu jeden wątek nie wejdzie w paradę innemu. Inne sposoby to muteksy, sekcje krytyczne, semafory itd. — wielowątkowość to temat rzeka, dlatego pasuje najpierw sporo poczytać, zanim się weźmie za pisanie kodu.

0

Tak wiem masz 100% racji! Myślę ,że nikt nie używa wątków tak jak tu jest zrobione włącznie ze mną. Tu pętla jest nieskończona, nie ma @synchronize jest goto itd. Tu wątki tylko łączą się z ip 127.0.0.1 i nasłuchują odbierają i wysyłają dane. W zmiennej wyślij jest to co ma być wysłane a przyjmij to co ma odebrać wątek servera. Nawet nie chodzi mi o sam wątek bo to jest jasne co piszesz, to procedury napisane na mega szybko mogą mieć pętle nieskończoną bo służą tylko do nauki Synapse i przesłania stringa np. przez tcp socket co nijak nie wychodzi.
Właściwie bez wątku też to może być na tym etapie ale dałem wątek bo wydało mi się ,że pozwoli chodzić pętli bez zatrzymywania wątku głównego gdzie mam 2edity i button.

1
  1. "Podczas wysyłki klientem program się zamyka/ jakiś wielki błąd się robi.".
    Czy "jakiś wielki błąd" ma jakiś numer, komunikat czy cokolwiek co by zdradzało jego znaczenie?

  2. Próbowałeś użyć debuggera?

  3. " funkcja getip odczytuje IP komputera aktualny" - a dalej piszesz że łączysz sie z "127.0.0.1". To jak w końcu? Sprawdziłeś może czy jakiś firewall na PC czegoś nie blokuje?

  4. Wiem jak to się pisze programy na odwal-się bo sam tak często robię ale jest kilka drobiazgów o których należy pamiętać jeśli to ma działać. Jedną z takich rzeczy jest synchronizacja przy współużywaniu danych, a zwłaszcza jak robi to się z dość upierdliwymi stringami. Tu możesz mieć masę problemów i to czasem nie robiąc niczego dziwnego. Nie wnikając w szczegóły - zrób z tego shortstring albo wrzuć je do wątków.

edit.
5. A patrzyłeś na przykłady dostępne w sieci? Czy serwer właśnie tak się ustawia?

0

@dziobu
Ad1) zamyka się aplikacja bez podania błędu jakiegokolwiek - znika.
Ad2) łączenie jest w wątku ,debugger tego nie widzi, błąd następuje poza wątkiem głównym. Nie ma zderzenia z systemem i z wątkiem głównym bo żadnych komponentów w wątkach nie modyfikuję.
Chyba że mimo wszystko dać synchronize do łączenia - choć to da rezultat w postaci zatrzymania działania aplikacji na czas przesyłu danych a tego chcę uniknąć.
Ad3) getip odczytuje aktualny adres ale on zawsze jest równy i tak 127.0.0.1
Ad4) zgadza się że szybko napisane dla sprawdzenia z czym to się je , nie zaimplementowałem tego do żadnego mojego programu
Ad5) tak przykład prosty ze strony jakiejś anglojęzycznej , najprostszy jaki znalazłem.

1

Jak nie potrafisz/jest problem z debuggowaniem wątków to stwórz dwie osobne aplikacje i tam uruchom kod w głównym wątku.
Polecam też może zacząć od zrobienia połowy, tj napisz swój serwer a połącz się do niego np z putty czy innego telnetu (czyli w zasadzie dokładnie to samo co robisz gdyż telnet wysyła na głupa tekst). Bo teraz tworzysz dwie strony komunikacji jednocześnie i nawet nie wiesz z czym masz problem.

btw.
To Delphi czy Lazarus?

0
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
  blcksock, synsock;

 type
  tserver = class(TThread)
          private
           { Private declarations }

          protected  procedure execute; override;
  end;

 type
     tclient = class(TThread)
     private
     { Private declarations }
     public

     protected  procedure execute; override;
     end;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Timer1: TTimer;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;
  klient,serwer:TTCPBlockSocket;
  ip,port:string;
  timeout:integer;
  WysylanyTekst:string;
  PrzyjmowanyTekst:string;

implementation

{$R *.lfm}

{ TForm1 }

function  GetIP: string;
var
  TcpSock: TTCPBlockSocket;
  ipList: TStringList;
begin
  Result := '';
  ipList := TStringList.Create;
  try
    TcpSock := TTCPBlockSocket.create;
    try
      TcpSock.ResolveNameToIP(TcpSock.LocalName, ipList);
      Result := ipList.CommaText;
    finally
      TcpSock.Free;
    end;
  finally
    ipList.Free;
  end;
end;




procedure tclient.execute;
begin
klient := TTCPBlockSocket.Create;
klient.Bind(ip,port);
klient.Connect (ip, port);
while not terminated do begin
      if wysylanyTekst<>'' then begin
         if klient.CanWrite(timeout) then begin
            klient.SendString (wysylanyTekst+#13#10);
            if klient.LastError<>0 then przyjmowanyTekst:='Błąd wysyłki';
            end;
         wysylanyTekst:='';
         end;
sleep(100);
end;
end;

procedure tserver.execute;
begin
while not terminated do begin
      serwer := TTCPBlockSocket.Create;
      serwer.Bind (ip, port);
      serwer.Listen;
      if serwer.CanRead(timeout) then begin
         przyjmowanyTekst:=serwer.RecvString(timeout);
         end;
         sleep(100);
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
   ser:tserver;
   cli:tclient;
begin
     port:='40516';
     timeout:=10000;
     ip:=getip;
     ser:=tserver.Create(false);
     cli:=tclient.Create(false);
     end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  wysylanyTekst:=form1.Edit1.Text;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  form1.Caption:='Client ip:'+ip+' klient  napisał: '+przyjmowanyTekst;
end;

end.


program project1;

{$mode objfpc}{$H+}

uses
  {$IFDEF UNIX}
  cthreads,
  {$ENDIF}
  Interfaces, // this includes the LCL widgetset
  Forms, Unit1
  { you can add units after this };

{$R *.res}

begin
  RequireDerivedFormResource:=True;
  Application.Scaled:=True;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
0

Podstawiłem zamiast:

if klient.LastError<>0 then przyjmowanyTekst:='Błąd wysyłki'; 

NA

if klient.LastError<>0 then przyjmowanyTekst:=klient.GetErrorDescEx;  

już się nie wiesza ale wyskakuje (na belce gdzie wyświetlam komunikat po stronie klienta) connection time out

1

Czyli nie masz połączenia.
To teraz tak:

  1. Masz firewalla?
  2. Jesteś pewien że "getip" zwraca 172.0.0.1? Ale bez żadnych znaków dodatkowych czy innych śmieci?
0

ad1) Była zapora zdjąłem ją teraz nie ma czekania na odpowiedź ze strony zapory czy się zgadzam, ale nadal zwraca timeout
ad2) no to co zwrociła funkcja getip jest wrzucane prosto z niej jako ip. na belce jest napis ip: 127.0.0.1 zarówno dla serwera jak i klienta.
Może powiniennem pod windowsem sprawdzić to samo....

0

Wygląda na to że Twój serwer nie działa - zobacz co jeszcze inni robią:
(to że masz jakiś przykład to niewiele znaczy; może dotyczyć innej wersji i zwyczajnie nie ruszy, coś jak z Indy)

procedure   TTCServer.Execute;   // основной цикл
var
  ClientSock:TSocket;
begin
  with sock do
    begin
      CreateSocket;
      setLinger(true,10000);
      bind('127.0.0.1','80');
      listen;
      repeat
        if terminated then break;
        if canread(10000) then
          begin
            ClientSock:=accept;
            if lastError=0 then TTCClient.create(ClientSock);
          end;
      until false;
    end;
end;

(https://sites.google.com/site/timpascallib/surum-burum/klient-i-server-dla-seti-tor)

0

W Windows identycznie jest. Różnica taka ,że getip dokłada dodatkowe znaki, przecinki, które ściąłem.
Dziękuję.

0

To teraz pytanie - jak używasz debuggera że tego nie wyłapałeś? :D

W sumie to zwracasz sobie "CommaText" co z natury porządkuje dane. Dla wielu kart sieciowych powinno Ci zwrócić wiele adresów więc serwer i tak nie ruszy.

0

W osx debuggera nie mogę użyć bo się lazarus wiesza(Kilka kompilacji zrobi po czym zawiesza się tak że nie można zabić procesu więc trzeba komputer wyłączyć) więc kompiluję projekt i szukam , patrzę jak działa.
Pewne rzeczy w windows działają inaczej.
I jak wielu rzeczy przykro, tak tutaj pisząc w osx w wiekszości oduczyłem się uruchamiać program z debugerem.

3

@Windowbee: jeśli masz problem z debugowaniem wątków, to zawsze możesz wyświetlać dane debugowania w oknie konsoli, za pomocą zwykłego WriteLn. Program będzie działał nieprzerwanie, a Ty na bieżąco będziesz widział ważne dane w konsoli.

Nie wiem jak w macOS, ale pod Windows wystarczy w ustawieniach odznaczyć opcję Win32 GUI application (-WG) i dzięki temu okno konsoli będzie otwierane dodatkowo (oprócz głównego okna programu). Można też skorzystać z dyrektywy {$APPTYPE CONSOLE} — ten sam efekt.

0

Udało mi się znaleźć jak stworzyć prosty serwer - taki dla ułomnych ale kod działa. Poniżej wstawiam unit1 z klientem i zaraz potem unit1 z serwerem. Po uruchomieniu przesyła stringa z edit1 klienta do serwera a w odpowiedzi dostaje stringa z edit1 serwera. Button1 na formie serwera tworzy serwer i znika. W form1.caption klienta jest ip odczytany, odpowiedź serwera oraz ew. błąd - opisowo. Teraz pracuję nad sposobem odczytania ip przydzielanego dla komputera przez główny serwer sieci domowej 192.168.0.1. Z jakiegoś powodu getip zawsze czyta ip= 127.0.0.1

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
  blcksock, synsock;

 type
     tclient = class(TThread)
     private
     { Private declarations }
     public

     protected  procedure execute; override;
     end;
//***********************
type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Timer1: TTimer;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;
  klient:TTCPBlockSocket;
  ip,port:string;
  timeout:integer;
  WysylanyTekst:string;
  PrzyjmowanyTekst:string;

implementation

{$R *.lfm}

{ TForm1 }

function  GetIP: string;
var
  TcpSock: TTCPBlockSocket;
  ipList: TStringList;
begin
  Result := '';
  ipList := TStringList.Create;
  try
    TcpSock := TTCPBlockSocket.create;
    try
      TcpSock.ResolveNameToIP(TcpSock.LocalName, ipList);
      Result := ipList.CommaText;
    finally
      TcpSock.Free;
    end;
  finally
    ipList.Free;
  end;
end;




procedure tclient.execute;
var
  s:string;
begin
klient := TTCPBlockSocket.Create;

while not terminated do begin
klient.Connect(ip, port);
         if klient.LastError=0 then begin;
            if klient.CanWrite(timeout) then begin
            klient.SendString(wysylanytekst+#13#10);
            PrzyjmowanyTekst:=klient.RecvString(1000);
            end;
         end;
if klient.LastError<>0 then PrzyjmowanyTekst:=klient.GetErrorDescEx;
klient.ResetLastError;
klient.CloseSocket;
sleep(1000);
end;
end;


procedure TForm1.FormCreate(Sender: TObject);
var
   cli:tclient;
begin
     port:='40516';
     timeout:=1000;
     ip:=getip;
     wysylanytekst:='Wiadomosc dla serwera';
     form1.Button1.Caption:='Wyślij edit1';
     cli:=tclient.Create(false);
     end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  wysylanyTekst:=form1.Edit1.Text;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  form1.Caption:='Client ip:'+ip+' serwer  odpowiedział: '+przyjmowanyTekst;
end;

end. 
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
  blcksock, synsock,process,synautil;
//****************
 type
  tserver = class(TThread)
          private
           { Private declarations }
           procedure wpisz;
           procedure AttendConnection(ASocket: TTCPBlockSocket);
           procedure connect;
          protected  procedure execute; override;
  end;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);

  private

  public

  end;

var
  Form1: TForm1;
  klient,serwer:TTCPBlockSocket;
  ip,port:string;
  timeout:integer;
  WysylanyTekst:string;
  PrzyjmowanyTekst:string;

implementation

{$R *.lfm}

{ TForm1 }
procedure tserver.AttendConnection(ASocket: TTCPBlockSocket);
var
  timeout: integer;
  s: string;
  method, uri, protocol: string;
  OutputDataString: string;
  ResultCode: integer;

begin
  timeout := 12000000;
  s := ASocket.RecvString(timeout);
  przyjmowanytekst:=s;
  synchronize(@wpisz);
  asocket.SendString(wysylanytekst+#13#10);
end;

 procedure tserver.connect;
var
  ListenerSocket, ConnectionSocket: TTCPBlockSocket;

begin
  ListenerSocket := TTCPBlockSocket.Create;
  ConnectionSocket := TTCPBlockSocket.Create;

  ListenerSocket.CreateSocket;
  ListenerSocket.setLinger(true,10);
  ListenerSocket.bind(ip,port);
  ListenerSocket.listen;
  repeat
    if ListenerSocket.canread(1000) then
    begin
      ConnectionSocket.Socket := ListenerSocket.accept;
      if connectionsocket.LastError<>0 then
      przyjmowanytekst:=('Error code: '+ inttostr(ConnectionSocket.lasterror));
      AttendConnection(ConnectionSocket);

    end;
  until false;
   ConnectionSocket.CloseSocket;
  ListenerSocket.Free;
  ConnectionSocket.Free;

  end;

 function  GetIP: string;
 var
   TcpSock: TTCPBlockSocket;
   ipList: TStringList;
 begin
   Result := '';
   ipList := TStringList.Create;
   try
     TcpSock := TTCPBlockSocket.create;
     try
       TcpSock.ResolveNameToIP(TcpSock.LocalName, ipList);
       Result := ipList.CommaText;
     finally
       TcpSock.Free;
     end;
   finally
     ipList.Free;
   end;
 end;
lenovo
procedure tserver.wpisz;
begin
     form1.Memo1.Lines.Add(przyjmowanytekst);
     wysylanytekst:=form1.Edit1.Text;
end;

procedure tserver.execute;
begin
     connect;
     synchronize(@wpisz);
end;
//***************
procedure TForm1.FormCreate(Sender: TObject);
var
   ser:tserver;
begin
     port:='40516';
     ip:=getip;
     end;
//*******************
procedure TForm1.Button1Click(Sender: TObject);
var
   ser:tserver;
begin
  ser:=tserver.Create(false);
  button1.Visible:=false;
end;

end.

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