reklama

Umíme to s Delphi: 162. díl – streamy a sockety: dokončení

V dnešním článku společně dokončíme vytváření ukázkové aplikace, na níž si demonstrujeme používání síťových (socketových) datových proudů – streamů. Před týdnem jsme implementovali komunikační část aplikace, dnes se budeme zabývat zpracováním dat a vytvořením uživatelského rozhraní.

Vítejte u dalšího dílu našeho programátorského seriálu. Dnes bezprostředně navážeme na to, co jsme si řekli před týdnem, kdy jsme se začali zabývat síťovými sockety a jejich souvislostmi s datovými proudy (streamy).

V minulém článku jsme společně začali vytvářet ukázkovou aplikaci, která uvedené problémy prakticky demonstruje. Připomeňme pouze účel a činnost aplikace:

  • 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

Vzhledem k tomu, co od aplikace žádáme, a vzhledem k tomu, čím se v poslední době náš seriál zabývá, je patrné, že k realizaci aplikace jsme zvolili dva koncepty:

  • síťovou komunikaci prostřednictvím socketů,
  • stahování dat ze serveru prostřednictvím datového proudu.

Pokud vám z nějakého důvodu unikl článek zveřejněný před týdnem, vřele doporučuji se k němu vrátit a přečíst si nejdřív. V dnešním článku totiž bezprostředně navážeme v místě, kde jsme před týdnem skončili, což je kdesi uprostřed procesu tvorby ukázkové aplikace. Pokud jste minule nezačali aplikaci vytvářet, nebudete dnes moci nikterak pokročit. Ještě jednou tedy prosím všechny čtenáře, aby neváhali a podívali se nejprve na článek z minulého týdne.

Ukázková aplikace – pokračování z minula

Před týdnem jsme realizovali následující kroky ve vytváření naší ukázkové aplikace:

  • modifikovali jsme sekci Uses hlavního formuláře
  • definovali jsme třídu vlákna TSocketThread
  • implementovali jsme konstruktor a destruktor této třídy
  • vytvořili jsme tělo výkonné metody vlákna – metody TSocketThread.Execute
  • implementovali jsme jednoduchou nastavovací metodu TSocketThread.SetGender
  • vytvořili jsme metodu TSocketThread.ThreadDone spouštěnou v okamžiku, kdy vlákno dokončí svou činnost

Máme za sebou tedy „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. Jedná se také o tu část, která má co do činění s vlákny, se sockety a se streamy.

Druhá část aplikace, kterou vytvoříme dnes, bude mít za úkol data zpracovat a zobrazit. Budeme tedy muset vytvořit design formuláře. K tomu účelu bude nutné vložit na formulář několik komponent. Ještě předtím však nesmíme zapomenout změnit jméno (vlastnost Name) hlavního formuláře Form1 na MainForm (neboť toto jméno jsme používali ve zdrojovém kódu minule).

Poté vložte na formulář následující komponenty:

  • 2 komponenty RadioButton ze záložky Standard,
  • 2 komponenty Button ze záložky Standard,
  • 2 komponenty Label ze záložky Standard,
  • komponentu Edit ze záložky Standard,
  • komponentu ListView ze záložky Win32. Poklepejte myší na tuto komponentu a přidejte 4 sloupce: Jméno, Procento populace, Celkem procent, Podíl (viz obrázek)

Klepněte pro větší obrázek

Úvodní vlastnosti a popisky komponent nastavíme jak je naším zvykem v obsluze události OnCreate hlavního formuláře (ještě jednou raději připomeňme, že hlavní formulář se dnes jmenuje MainForm, nikoliv tradičně Form1):

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Caption := `Ukazka sitovych streamu`;
  RadioButton1.Caption := `Muzska jmena`;
  RadioButton1.Checked := True;
  RadioButton2.Caption := `Zenska jmena`;
  Button1.Caption := `Stahni data`;
  Button2.Caption := `Hledej`;
  Label1.Caption := ``;  // nastavi se za behu
  Label2.Caption := `Hledej: `;
  Edit1.Text := `John`;

  ListView1.Viewstyle := vsReport;
  FNamesList := TStringList.Create; // pro pozdejsi vyuziti
end;

Pojďme dál. Ošetříme událost OnClick tlačítka Button1, které bude sloužit ke stažení dat ze serveru. V obsluze události vytvoříme objekt vlákna TSocketThread, které jsme definovali minule. Vlákno je spuštěno a začne svůj běh. V závislosti na nastavení tlačítek RadioButton dojde k nastavení vlastnosti Gender na mužské nebo ženské jméno.

procedure TMainForm.Button1Click(Sender: TObject);
begin
 Button1.Enabled := False;
  // Vytvorime vlakno
  with TSocketThread.Create do
  begin
    if RadioButton1.Checked then
      Gender := `male`
    else
      Gender := `female`;
    Resume;
  end;
end;

Další věc, kterou musíme udělat, je zpracování dat poté, co jsou načtena ze serveru. Už víme, že vlákno poté, co načte všechna data, zavolá metodu ProcessData, která je zpracuje. Tuto metodu nyní musíme implementovat. Takže, nejprve přidejme hlavičku metody do definice třídy TMainForm. Použijeme sekci private, protože  třída TSocketThread je definována v téže jednotce. Když už něco přidáváme do definice třídy, přidejme i atribut FNameList, který využijeme později:

  private
    { Private declarations }
    FNamesList: TStringList;
    procedure ProcessData(Data: string);

Nyní pojďme na samotné zpracování dat (metodu ProcessData). HTTP odezva nám vrátí hlavičku, která nás příliš nezajímá: začátek zajímavých dat bezprostředně následuje po dvou párech oddělovačů nové řádky (dvojice #10#13). Budeme tedy zpracovávat data až od této dvojice:

procedure TMainForm.ProcessData(Data: string);
var
  DataStart: Integer;
  CurrentPos: PChar;
  Name: string;
  ListItem: TListItem;

  function ReadNextToken: string;
  begin
    Result := ``;
    // Vse je oddeleno bilymi znaky
    // Nejdriv preskocie vsechny bile znaky na zacatku
    while (CurrentPos^ in [#10, #13, ` `]) do
      Inc(CurrentPos);

    // nacitame znaky, dokud nenajdeme bile znaky
    while not (CurrentPos^ in [` `, #0, #10, #13]) do
    begin
      Result := Result + CurrentPos^;
      Inc(CurrentPos);
    end;
  end;

begin
  // velmi jednoduchy parser

  // nejprve preskocime http hlavicku (az ke dvema dvojicim CR LF)

  DataStart := Pos(#13#10#13#10, Data);
  if DataStart = 0 then
    raise Exception.Create(`HTTP odezva neobsahuje pozadovana data`);

  ListView1.Items.BeginUpdate;
  try
    ListView1.Items.Clear;
    // FNamesList je TStringList pouzity k uchovani seznamu jmen
    FNamesList.Clear;

    // zaciname za dvojicemi CR LF, pouzijeme ukazatel
    CurrentPos := PChar(@Data[DataStart + 4]);
    while (CurrentPos <> nil) and (CurrentPos^ <> #0) do
    begin
      Name := ReadNextToken;
      if Name <> `` then
      begin
        // vytvorime seznam
        ListItem := ListView1.Items.Add;
        ListItem.Caption := Name;
        ListItem.SubItems.Add(ReadNextToken);
        ListItem.SubItems.Add(ReadNextToken);
        ListItem.SubItems.Add(ReadNextToken);
        // pridame jmeno do seznamu - pozdeji bude snadne vyhledavani
        FNamesList.Add(Name);
        // Budeme si na polozku udrzovat ukazatel
        FNamesList.Objects[FNamesList.Count - 1] := ListItem;
        Label1.Caption := `Zpracovavam data...`;
        Application.ProcessMessages;
      end
      else
        Break;
    end;
  finally
    ListView1.Items.EndUpdate;
  end;
  // setridime seznam jmen
  FNamesList.Sort;
  Label1.Caption := `Hotovo`;
  Button1.Enabled := True;
end;

V metodě OnCreate hlavního fomuláře jste si možná všimli vytvoření seznamu FListNames. Tento seznam, v němž uchováváme načtená jména, musíme při skončení práce zase uvolnit z paměti – ošetřeme tedy metodu OnDestroy hlavního formuláře:

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FNamesList.Free;
end;

Jsme skoro hotovi! Poslední věc, kterou musíme učinit, je naprogramování podpory vyhledávání jmen. Jinak řečeno, uživatel by měl mít možnost vyhledat jednoduše konkrétní jméno v seznamu. Ošetříme tedy událost OnClick tlačítka Button2. Obsluha bude velmi jednoduchá – využijeme s výhodou toho, že k uchování seznamu jsme zvolili StringList, který v sobě přímo integruje podporu vyhledávání:

procedure TMainForm.Button2Click(Sender: TObject);
var
  Index: Integer;
begin
  Index := FNamesList.IndexOf(UpperCase(Edit1.Text));
  if (Index >= 0) then
  begin
    ListView1.Selected := TListItem(FNamesList.Objects[Index]);
    ListView1.Selected.MakeVisible(False);
    ListView1.SetFocus;
  end
  else
    raise Exception.Create(`Nasledujici jmeno nenalezeno: ` + Edit1.Text);
end;

Toť vše, aplikace je hotova, nezbývá nežli ji vyzkoušet!

Klepněte pro větší obrázek

 

Zdrojový kód

Závěrem si jako obvykle uvedeme kompletní zdrojový kód celého příkladu:

unit Unit1;

interface

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

 

type
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;

 

  TMainForm = class(TForm)
    RadioButton1: TRadioButton;
    RadioButton2: TRadioButton;
    Button1: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Edit1: TEdit;
    Button2: TButton;
    ListView1: TListView;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    FNamesList: TStringList;
    procedure ProcessData(Data: string);
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

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;


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 /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;


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;

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;

{$R *.dfm}

 


procedure TMainForm.FormCreate(Sender: TObject);
begin
  Caption := `Ukazka sitovych streamu`;
  RadioButton1.Caption := `Muzska jmena`;
  RadioButton1.Checked := True;
  RadioButton2.Caption := `Zenska jmena`;
  Button1.Caption := `Stahni data`;
  Button2.Caption := `Hledej`;
  Label1.Caption := ``;  // nastavi se za behu
  Label2.Caption := `Hledej: `;
  Edit1.Text := `John`;

  ListView1.Viewstyle := vsReport;
  FNamesList := TStringList.Create; // pro pozdejsi vyuziti 
end;

 


procedure TMainForm.Button1Click(Sender: TObject);
begin
 Button1.Enabled := False;
  // Vytvorime vlakno
  with TSocketThread.Create do
  begin
    if RadioButton1.Checked then
      Gender := `male`
    else
      Gender := `female`;
    Resume;
  end;
end;

procedure TMainForm.ProcessData(Data: string);
var
  DataStart: Integer;
  CurrentPos: PChar;
  Name: string;
  ListItem: TListItem;

  function ReadNextToken: string;
  begin
    Result := ``;
    // Vse je oddeleno bilymi znaky
    // Nejdriv preskocie vsechny bile znaky na zacatku
    while (CurrentPos^ in [#10, #13, ` `]) do
      Inc(CurrentPos);

    // nacitame znaky, dokud nenajdeme bile znaky
    while not (CurrentPos^ in [` `, #0, #10, #13]) do
    begin
      Result := Result + CurrentPos^;
      Inc(CurrentPos);
    end;
  end;

begin
  // velmi jednoduchy parser

  // nejprve preskocime http hlavicku (az ke dvema dvojicim CR LF)

  DataStart := Pos(#13#10#13#10, Data);
  if DataStart = 0 then
    raise Exception.Create(`HTTP odezva neobsahuje pozadovana data`);

  ListView1.Items.BeginUpdate;
  try
    ListView1.Items.Clear;
    // FNamesList je TStringList pouzity k uchovani seznamu jmen
    FNamesList.Clear;

    // zaciname za dvojicemi CR LF, pouzijeme ukazatel
    CurrentPos := PChar(@Data[DataStart + 4]);
    while (CurrentPos <> nil) and (CurrentPos^ <> #0) do
    begin
      Name := ReadNextToken;
      if Name <> `` then
      begin
        // vytvorime seznam
        ListItem := ListView1.Items.Add;
        ListItem.Caption := Name;
        ListItem.SubItems.Add(ReadNextToken);
        ListItem.SubItems.Add(ReadNextToken);
        ListItem.SubItems.Add(ReadNextToken);
        // pridame jmeno do seznamu - pozdeji bude snadne vyhledavani
        FNamesList.Add(Name);
        // Budeme si na polozku udrzovat ukazatel
        FNamesList.Objects[FNamesList.Count - 1] := ListItem;
        Label1.Caption := `Zpracovavam data...`;
        Application.ProcessMessages;
      end
      else
        Break;
    end;
  finally
    ListView1.Items.EndUpdate;
  end;
  // setridime seznam jmen
  FNamesList.Sort;
  Label1.Caption := `Hotovo`;
  Button1.Enabled := True;
end;

 


procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FNamesList.Free;
end;

procedure TMainForm.Button2Click(Sender: TObject);
var
  Index: Integer;
begin
  Index := FNamesList.IndexOf(UpperCase(Edit1.Text));
  if (Index >= 0) then
  begin
    ListView1.Selected := TListItem(FNamesList.Objects[Index]);
    ListView1.Selected.MakeVisible(False);
    ListView1.SetFocus;
  end
  else
    raise Exception.Create(`Nasledujici jmeno nenalezeno: ` + Edit1.Text);
end;

end.

Na závěr

Dnes jsme dokončili ukázkovou aplikaci, na níž jsme si prakticky demonstrovali používání dalšího druhu streamů – socketových streamů. Věřím, že jste se nenechali odradit větším rozsahem aplikace   - její zajímavost doufám tento drobný problém bohatě vynahradila.

Témata článku: Software, Programování, Gender, Index, Private, Male, Female, Resume, Timeout, Destroy

4 komentáře

Nejnovější komentáře

  • Peter P 24. 10. 2006 9:41:19
    Prosim, viem zesa daju riadit dva pocitace z jedneho ked su tam nastavene...
  • ppalo, ppalo 20. 9. 2005 11:48:46
    mohli by ste my prosim pomoct? potrebujem pomocou streamov a socketov na...
  • palo 19. 9. 2005 18:33:40
    mohli by steny prosim pomoct potrebujem pomocou streamov a socketov na...
reklama
Určitě si přečtěte

UPC překopli páteřní kabel. V Brně i druhý den nejede internet ani kabelovka

UPC překopli páteřní kabel. V Brně i druhý den nejede internet ani kabelovka

** V Brně byl velký výpadek služeb UPC ** Důvodem je překopnutý páteřní kabel ** V některých lokalitách služby stále nefungují

5.  12.  2016 | Jakub Čížek | 102

17 expertek Microsoftu předpovědělo rok 2027. Splní se alespoň něco?

17 expertek Microsoftu předpovědělo rok 2027. Splní se alespoň něco?

** Zmizí klasické vyhledávače ** Budeme programovat buňky ** Kvantové počítače překonají šifry

6.  12.  2016 | Jakub Čížek | 36

11 tipů na dobrý stolní počítač: od základu po herní mašiny

11 tipů na dobrý stolní počítač: od základu po herní mašiny

** Postavte si stolní počítač! Máme pro vás 11 vzorových sestav s rozpisem komponent ** Většina tipů cílí na hráče, věnujeme se ale i základnímu PC a počítačům na střih videa ** Nadělte si nový počítač třeba pod stromeček

5.  12.  2016 | Adam Kahánek | 74

Nejlepší notebooky nad 20 tisíc: poradíme, které teď chcete

Nejlepší notebooky nad 20 tisíc: poradíme, které teď chcete

** V notebooku s cenou nad 20 tisíc nesmí chybět kvalitní displej a rychlé úložiště ** Za dalších deset tisíc můžete dostat navíc styl nebo výkonnější komponenty ** Vybírat můžete z různých velikostí i konstrukcí

8.  12.  2016 | Stanislav Janů | 82


reklama