Umíme to s Delphi: 161. díl – streamy a sockety: nic než praxe

V dnešním článku se zaměříme i na dosud nezmiňované datové proudy, například síťové. Ty se typicky používají ve spolupráci s vícevláknovým programováním k realizaci blokujícího síťového spojení. Vytvoříme ukázkovou aplikaci, která prostřednictvím streamů a socketů přečte a zpracuje soubor z internetového serveru.

V dnešním díle se na rozdíl od minulého článku zaměříme na ryzí praxi a společně krok za krokem vytvoříme aplikaci, která použije další druh streamu, s nímž jsme se dosud nesetkali. Bude se jednat o stream používaný pro čtení a zápis dat po počítačové síti; tento stream je reprezentován komponentou TSocketStream.

Nebudeme dlouho chodit okolo horké kaše, vrhneme se co nejdříve na vytváření ukázkové aplikace. Nezbytné teorie je vskutku minimum, tak pojďme na ni.

TSocketStream – co to je?

Jak jsem si řekli v předchozích částech seriálu věnovaných streamům, existuje jeden druh datových proudů, který se používá při práci s počítačovou sítí. Jedná se o stream SocketStream reprezentovaný komponentou TSocketStream.

Pokud jste v oblasti počítačových sítí úplnými nováčky a nevíte o nich v zásadě nic, a nebo pokud jste nikdy neslyšeli pojem „síťový socket“, pak doporučuji projít se zpět v čase a podívat se na 124. díl našeho seriálu a na díly následující. V částech 124 až 133 je totiž problematika sítí a socketů poměrně podrobně rozebrána, včetně ukázek a praktických příkladů.

Nyní předpokládejme, že do sítí alespoň částečně vidíte a že máte přibližné povědomí o tom, jak fungují sockety. Jediné, co bych v zájmu srozumitelnosti dnešního článku zdůraznil, je rozdíl mezi blokujícím a neblokujícím spojením. Tahle věc je důležitá nejen z hlediska následujícího zdrojového kódu, ale také z hlediska porozumění síťovým streamům.

V zásadě platí, že existují dva druhy síťového spojení: spojení blokující (blocking connection) a spojení neblokující (non-blocking connection). Pokud čtení či zápis dat ze/do socketu probíhá asynchronně, tedy pokud odesilatel a příjemce dat nejsou nikterak synchronizováni – domluveni, jedná se o neblokující spojení. Lze jej přirovnat ke komunikaci formou SMS zpráv: aplikace (uživatel mobilního telefonu) pošle data (SMS zprávu) přes socket (mobilní telefon) a dál pokračuje ve svém běhu (jde se sprchovat) bez ohledu na to, jak dopadlo doručení dat (SMS zprávy). Jinak řečeno – aplikace po odeslání zprávy ihned běží dále a nečeká na dokončení čtení (odesílání) dat. Není tedy blokována.

Druhá možnost je blokující spojení, které znamená pravý opak: aplikace je po odeslání zprávy blokována až do okamžiku, kdy je operace dokončena. Tento způsob spojení si lze představit jako telefonát: jedna aplikace (volající telefonista) je zablokovaná (čeká na lince), dokud se druhá aplikace (volaný telefonista) neozve (nepřijme hovor).

Pokud se rozhodneme použít ve své aplikaci neblokující spojení, je situace celkem jednoduchá, protože Delphi standardně poskytují prostředky (třeba uvnitř komponent ClientSocket a ServerSocket), pomocí kterých se dozvíme, že data jsou připravena pro čtení nebo pro zápis (např. události OnRead nebo OnWrite). V obsluze události pouze přečteme / zapíšeme dostupná data a je hotovo.

V případě blokujícího spojení však vzniká otázka: co se stane, pokud zavoláme blokující čtení a data nejsou pro čtení dosud připravena? Aplikace zamrzne do doby, kdy data budou připravena ke čtení.

Způsob, jakým tomuto „zmrznutí“ zamezit, spočívá v použití komponenty TocketStream. Pokud vytvoříme tento síťový stream, dostaneme i v případě blokujícího spojení do ruky nástroje, které jsme měli k dispozici v případě spojení neblokujícího: můžeme například nastavit timeout, po jehož dobu se má čekat na to, zda budou data k dispozici a po jehož vypršení se má předpokládat, že data k dispozici nejsou. Můžeme tak zamezit zamrznutí aplikace.

Takže, jaké to má důsledky pro naši ukázkovou aplikaci? Jak uvidíte později, „strčíme“ komponentu ClientSocket dovnitř vlákna, vytvoříme tedy vícevláknovou aplikaci, jejíž jedno vlákno se bude starat o (blokující) čtení. Uvnitř onoho čtecího vlákna, které naváže blokující spojení se serverem a které bude ze serveru číst data, vytvoříme síťový datový proud – síťový stream, jehož prostřednictvím budeme data ze serveru fyzicky číst.

Ukázková aplikace – co bude dělat?

Dost bylo teorie, dnešní článek bude plně praktický. Vytvoříme společně ukázkovou aplikaci: co bude v jejím popisu práce?

  • aplikace naváže spojení se serverem www.census.gov, který obsahuje údaje ze sčítání lidu v USA v roce 1990
  • aplikace stáhne soubor s četností jednotlivých křestních jmen,
  • aplikace bude schopna vyhledat požadované křestní jméno v seznamu a vypsat jeho četnost

Poznámka: aplikace je převzata z Borland Developer Network. Nevím přesně, zda obdobný soubor existuje i pro česká jména, přidržíme se tedy americké verze. Na postup vytváření aplikace nemá tento detail samozřejmě žádný vliv, jde pouze o ukázku.

Vytváření aplikace – krok za krokem

Pojďme se pustit do vytváření aplikace. Vytvořte v Delphi novou aplikaci. Do sekce Uses hlavního formuláře připište modul ScktComp:

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ScktComp;

Nyní vytvořte objekt vlákna, které se bude starat o čtení ze vzdáleného síťového serveru. Pokud nevíte nic o problematice vláken, doporučuji podívat se na 51. díl tohoto seriálu a na díly následující, v nichž najdete podrobný popis problematiky vláken a paralelního programování. Objekt vlákna bude umístěn v modulu hlavního formuláře, ihned za sekcí Uses, před deklarací třídy hlavního formuláře:

TSocketThread = class(TThread)
  private
    FClientSocket: TClientSocket;
    FData: string;
    FLastException: Exception;
    FGender: string;
    procedure SetGender(const Value: string);
  protected
    procedure Execute; override;
    procedure HandleThreadException;
    procedure ThreadDone;
  public
    constructor Create;
    destructor Destroy; override;
    property Gender: string read FGender write SetGender;
  end;

Nyní se pojďme podívat na sekci Implementation. Do ní připíšeme těla konstruktoru a destruktoru vlákna. V konstruktoru dojde k nastavení vlastností spojení a k nastavení požadované cílové adresy (www.census.gov). Destruktor pouze uvolní paměť:

constructor TSocketThread.Create;
begin
  inherited Create(True); // Intially suspended
  FreeOnTerminate := True;
  FClientSocket := TClientSocket.Create(nil);
  FClientSocket.Host := `www.census.gov`;
  FClientSocket.Port := 80; // HTTP port
  FClientSocket.ClientType := ctBlocking; // Blocking, so it is in a thread
end;

destructor TSocketThread.Destroy;
begin
  FClientSocket.Free;
  inherited;
end;

Dostáváme se k nejdůležitější části tvorby aplikace. Jak víte (a nebo jak jste si zopakovali při čtení článků o vláknech), základní výkonný kód vlákna se zapisuje do těla metody Execute. Vzhledem k tomu, co jsme si řekli výše (o tom, že použijeme blokující síťové spojení), musíme použít TWinStreamSocket – stream pro práci se síťovým socketem. Jak jsme si řekli, TWinStreamSocket nám umožní nejen číst data ze síťového streamu, ale také nastavit timeout pro případ, kdy něco selže a data nebudou k dispozici.

Takže, co bude naše metoda Execute dělat? Jednoduše se připojí k serveru, vytvoří objekt TWinSocketStream, a bude číst data tak dlouho, až budou všechna přečtena. To lze rozpoznat dvěma způsoby: buďto server vrátí nula bajtů (= žádné další bajty nejsou k dispozci) a nebo server uzavře síťový socket (= žádná další data nejsou k dispozici).

Podívejme se na zdrojový kód metody Execute. Bude umístěn v sekci implementation, ihned pod destruktorem vytvořeným o odstavec výše:

procedure TSocketThread.Execute;
var
  Buffer: array[0..1024-1] of Char;
  SockStream: TWinSocketStream;
  RequestString: string;
begin
  try
    FClientSocket.Active := True;
    FData := ``;
    FillChar(Buffer, SizeOf(Buffer), #0);

    // Create the socket stream with a 1 minute timeout
    SockStream := TWinSocketStream.Create(FClientSocket.Socket, 60000);
    try
      RequestString := Format(`GET /ftp/pub/genealogy/names/dist.%s.first HTTP/1.0`#13#10#13#10,
        [FGender]);
      // Send the HTTP request
      SockStream.Write(RequestString[1], Length(RequestString));
      // Read the response in 1024 chunks
      while (SockStream.Read(Buffer, SizeOf(Buffer)) <> 0) do
      begin
        FData := FData + Buffer;
        FillChar(Buffer, SizeOf(Buffer), #0);
        // Check for termination of the thread or closure of the socket
        if Terminated or not FClientSocket.Active then
          Exit;
      end;
    finally
      SockStream.Free;
      if FClientSocket.Active then
        FClientSocket.Active := False;
    end;
  except
    on E: Exception do
    begin
      if not(ExceptObject is EAbort) then
      begin
        FLastException := E;
        Synchronize(HandleThreadException);
      end;
    end;
  end;
  // Tell the main form that we are done
  Synchronize(ThreadDone);
end;

procedure TSocketThread.HandleThreadException;
begin
  Application.ShowException(FLastException); 
end;

Zdrojovým kódem se nebudeme zabývat nijak extrémně podrobně: autor originální verze upozorňuje pouze na skutečnost, že všechna potencionálně nebezpečná místa (což je celé tělo metody ) „zabaluje“ do kritických sekcí try – except a využívá tak mechanismu výjimek. Vzhledem ke specifikům práce s výjimkami uvnitř vláken jsou pak všechny výjimky ošetřovány prostřednictvím metody Synchronize, kterou používáme pro bezpečné paralelní provádění věcí, které by jinak při paralelním provádění mohly způsobovat problémy. Pro ošetření výjimky je pak implementována metoda HandleThreadException (viz konec zdrojového kódu, který jsme právě napsali): v naší jednoduché verzi spočívá ošetření výjimky pouze v tom, že vypíšeme příslušnjé chybové hlášení (ShowException).

Další věc, kterou stojí za to zdůraznit, je soukromý atribut Gender objektu vlákna. S tímto atributem (který obsahuje informaci o tom, zda si uživatel přeje pracovat s mužskými a nebo s ženskými jmény, neboť na serveru existují oddělené soubory pro obě pohlaví) je také svázána „nastavovací“ metoda SetGender, která do atributu Gender zapíše buďto „male“ pro mužské pohlaví nebo „female“ v případě, že uživatel zvolil dámy. Řetězec „male“, resp. „female“ je následně rovnou použit při konstrukci jména souboru, který se budeme pokoušet stáhnout ze serveru.

Nicméně, pojďme se raději podívat na zdrojový kód zmíněné nastavovací metody SetGender. Kód je umístěn bezprostředně pod tělem metod Execute a HandleThreadException vytvořených v předchozím kroku:

procedure TSocketThread.SetGender(const Value: string);
begin
  if (Value = `male`) or (Value = `female`) then
    FGender := Value
  else
    raise Exception.CreateFmt(`Unknown gender (%s) to search for`,
      [Value]);
end;

V závěru těla metody Execute jste si možná povšimli volání metody ThreadDone prostřednictvím metody Synchronize. Cílem tohoto volání není nic jiného než sdělit hlavnímu formuláři, že vlákno „je hotovo“. Hlavní formulář se tedy dozví, že vláknu se zdařilo (a nebo také ne, to je celkem jedno, nicméně důležité je, že čtení skončilo) načíst data ze streamu a může data dále zpracovávat (např. je zobrazit na ploše apod.). Zde je zdrojový kód metody ThreadDone; bude umístěn ihned pod tělem SetGender, které jsme vytvořili v předchozím kroku:

procedure TSocketThread.ThreadDone;
begin
  // The VCL is not thread safe. Accessing MainForm from inside
  // a thread would be bad, unless if it is inside a procedure
  // that was called via Synchronize.
  MainForm.ProcessData(FData);
end;

Funkci ProcessData, která bude zpracovávat data dodaná vláknem, vytvoříme za týden.

Na závěr

Tímto jsme dokončili „komunikační“ část aplikace, tedy tu část, která se stará o získávání dat ze vzdáleného síťového (internetového) serveru. Druhá část aplikace bude mít za úkol data zpracovat a zobrazit. Ta již bude bezprostředně pracovat s vizuálními komponentami umístěnými na formuláři. Těšte se, za týden jsme tu zpět.

Diskuze (1) Další článek: Portál Centrum zaznamenává díky VyVoleným raketový růst návštěvnosti

Témata článku: Software, Programování, Vlákno, FEMA, Zmrznutí, Timeout, Str, Předchozí krok, Mužské pohlaví, Ukázková aplikace, Běžná praxe, Volaná zpráva, Male, Inside, Druhá aplikace, Horký objekt, Originální verze, Křestní jméno, Nota, Praxe, Nebezpečná událost, DEL, Search for, SOCKS, Socket


Určitě si přečtěte

Jak funguje kontroverzní program, který ženám krade plavky. Mají se čeho bát?

Jak funguje kontroverzní program, který ženám krade plavky. Mají se čeho bát?

** Strojové učení ještě nepřitáhlo takový zájem jako na začátku prázdnin ** Ne, umělá inteligence nenašla lék na rakovinu ** Naučila se svlékat ženy nejen z plavek

Jakub Čížek | 35

Proč byste měli rozmazávat SPZ aut na fotkách, které vystavujete na web

Proč byste měli rozmazávat SPZ aut na fotkách, které vystavujete na web

** Na fotkách aut nahraných na web je dobré rozmazat SPZ ** Značku dokáže z obrázku přečíst Google i Facebook ** SPZ může naplnit podstatu osobního údaje

Karel Kilián | 67

Srovnali jsme česká města s IBM, Googlem nebo třeba Samsungem. Podívejte se, v jaké firmě „žijete“

Srovnali jsme česká města s IBM, Googlem nebo třeba Samsungem. Podívejte se, v jaké firmě „žijete“

** Nadnárodní korporace zaměstnávají stovky tisíc lidí ** Mají tedy velikost metropolí ** Srovnali jsme je s českými krajskými městy

Jakub Čížek | 15

37 nejstrašnějších počítačů, které jste kdy viděli

37 nejstrašnějších počítačů, které jste kdy viděli

** Přehled nejhorších počítačů na světě ** Šílené konstrukce a materiály ** Jak to dopadne, když se o počítač nestaráte

Karel Javůrek | 24

Zkusili jsme Ryzen 7 4800HS v notebooku Asus: drtí Intel výkonem a umí být potichu

Zkusili jsme Ryzen 7 4800HS v notebooku Asus: drtí Intel výkonem a umí být potichu

** Nové osmijádro AMD pro herní notebooky překvapuje výkonem ** V rámci notebooku ROG Zephyrus G15 umí být tiché i výkonné** Rozhodnou hlavně prodávané konfigurace s lepší grafikou

Tomáš Holčík | 70

Dnes nastal konec Windows 7. Ale nepropadejte panice, počítač vám nastartuje i zítra

Dnes nastal konec Windows 7. Ale nepropadejte panice, počítač vám nastartuje i zítra

** Dnes končí podpora Windows 7 a systém formálně umírá ** Co to ale znamená v praxi a bude mi PC fungovat i zítra? ** A mohu i v lednu 2020 zdarma přejít na Windows 10?

Jakub Čížek | 121



Aktuální číslo časopisu Computer

Velký test autokamer

Test ATX skříní

Jak surfovat pohodlně

Sportovní aplikace

Jak funguje procesor